Negotiation
TnsAI.Coordination provides a pluggable negotiation framework with 4 built-in protocols, configurable concession strategies, and a unified `NegotiationExecutor` that resolves the correct protocol from configuration.
NegotiationExecutor
The NegotiationExecutor is the main entry point for all negotiations. You give it agents, a configuration (protocol type, max rounds, timeout, starting offer), and it runs the negotiation from start to finish. It maintains a registry of NegotiationProtocol implementations keyed by NegotiationType, and dispatches negotiations to the matching protocol.
NegotiationExecutor executor = new NegotiationExecutor();
// Built-in protocols are registered automatically:
// - BilateralNegotiation
// - MonotonicConcessionProtocol
// - AuctionAdapter (handles ENGLISH_AUCTION, DUTCH_AUCTION, SEALED_BID, VICKREY)
// - ContractNetAdapter
NegotiationOutcome outcome = executor.execute(
List.of(buyerAgent, sellerAgent),
new NegotiationConfig(
NegotiationType.BILATERAL,
10, // maxRounds
Duration.ofMinutes(5), // timeout
50.0, // reservationValue (walk-away point)
100.0, // initialOffer
new LinearConcession() // concession strategy
)
);Listeners
You can attach listeners to get notified when a negotiation starts and when it completes. This is useful for logging, monitoring, or triggering follow-up actions.
executor.addListener(new NegotiationListener() {
@Override
public void onNegotiationStarted(NegotiationConfig config) { }
@Override
public void onNegotiationCompleted(NegotiationOutcome outcome) {
System.out.println("Agreed: " + outcome.agreed());
}
});Custom Protocols
If the built-in protocols don't fit your use case, you can implement your own NegotiationProtocol and register it with the executor.
Register a custom protocol to extend or replace built-in behavior:
executor.register(new MyCustomProtocol()); // implements NegotiationProtocolFour Negotiation Protocols
TnsAI ships with four negotiation protocols that cover the most common multi-agent negotiation patterns, from simple two-party bargaining to competitive auctions.
Bilateral
The simplest protocol: two agents take turns making offers and counter-offers until they agree or time runs out. This is the classic buyer-seller negotiation pattern. Each round, the current agent makes a concession based on its ConcessionStrategy.
NegotiationConfig config = new NegotiationConfig(
NegotiationType.BILATERAL,
10, // max 10 rounds
Duration.ofMinutes(5),
50.0, // minimum acceptable value
100.0, // starting offer
new LinearConcession()
);
NegotiationOutcome result = executor.execute(List.of(agent1, agent2), config);MonotonicConcession
A variant of bilateral negotiation with a stricter rule: each party must make monotonically decreasing (or equal) offers. No party can raise their demand after conceding. Guarantees convergence when both parties concede.
NegotiationConfig config = new NegotiationConfig(
NegotiationType.MONOTONIC_CONCESSION,
15,
Duration.ofMinutes(3),
30.0,
100.0,
new ExponentialConcession()
);Auction
Auctions allocate tasks or resources through competitive bidding. This is useful when multiple agents can do the same job and you want to find the best price or fit. The AuctionAdapter wraps the Auction class and handles all four auction types through a single negotiation interface.
// Via NegotiationExecutor
NegotiationConfig config = new NegotiationConfig(
NegotiationType.VICKREY, // or ENGLISH_AUCTION, DUTCH_AUCTION, SEALED_BID
10,
Duration.ofMinutes(2),
0.0,
100.0,
new LinearConcession()
);
NegotiationOutcome result = executor.execute(bidders, config);See the dedicated Auction section below for direct usage.
ContractNet
The Contract Net protocol is a structured approach where an initiator broadcasts a call for proposals to multiple agents, collects their bids, and selects the best one. It is the FIPA Contract Net protocol adapted for the negotiation framework. An initiator broadcasts a call for proposals, collects bids, selects a winner, and monitors execution.
NegotiationConfig config = new NegotiationConfig(
NegotiationType.CONTRACT_NET,
5,
Duration.ofMinutes(3),
0.0,
-1.0, // auto initial offer
new LinearConcession()
);
NegotiationOutcome result = executor.execute(contractorAgents, config);Concession Strategies
A concession strategy controls how aggressively an agent lowers its asking price over successive rounds of negotiation. Different strategies produce different bargaining behaviors. The ConcessionStrategy functional interface computes the next offer value each round:
@FunctionalInterface
public interface ConcessionStrategy {
double nextOffer(int round, int maxRounds, double reservationValue,
double currentValue, List<NegotiationOffer> history);
}Built-in Strategies
TnsAI provides four concession strategies out of the box. You can also define a custom strategy using a lambda.
| Strategy | Class | Behavior |
|---|---|---|
| Linear | LinearConcession | Constant concession rate per round. Concedes (current - reservation) / remainingRounds each round. |
| Exponential | ExponentialConcession | Concedes slowly at first, accelerates near deadline. |
| Tit-for-Tat | TitForTatConcession | Mirrors opponent's last concession amount. Cooperative if opponent cooperates. |
| Time-Dependent | TimeDependentConcession | Concession rate increases as deadline approaches. |
// Linear: steady concession
ConcessionStrategy linear = new LinearConcession();
// Exponential: holds firm early, concedes more later
ConcessionStrategy exponential = new ExponentialConcession();
// Tit-for-Tat: reciprocates opponent's concession behavior
ConcessionStrategy tft = new TitForTatConcession();
// Custom lambda
ConcessionStrategy custom = (round, maxRounds, reservation, current, history) -> {
double progress = (double) round / maxRounds;
return current - (current - reservation) * progress * progress;
};NegotiationConfig
The configuration record that defines all parameters for a negotiation session: which protocol to use, how many rounds, the timeout, the walk-away point, and the concession strategy.
| Field | Type | Description |
|---|---|---|
type | NegotiationType | Protocol type (required) |
maxRounds | int | Maximum negotiation rounds (\>= 1) |
timeout | Duration | Overall negotiation timeout |
reservationValue | double | Minimum acceptable value (walk-away point) |
initialOffer | double | Starting offer value (-1 for auto) |
concessionStrategy | ConcessionStrategy | Strategy for computing next offer |
NegotiationOutcome
The result of a completed negotiation. It tells you whether the agents reached an agreement, what the final offer was, how many rounds it took, and the reason for failure if no deal was reached.
| Field | Type | Description |
|---|---|---|
agreed | boolean | Whether agreement was reached |
acceptedOffer | NegotiationOffer | The accepted offer (null if no agreement) |
allOffers | List<NegotiationOffer> | All offers made during negotiation |
roundsUsed | int | Number of rounds conducted |
durationMs | long | Total negotiation duration |
finalState | NegotiationState | Final state: AGREED, FAILED, TIMEOUT, DEADLOCK |
failureReason | String | Reason for failure (null if agreed) |
Auction Mechanism (Direct Usage)
While the NegotiationExecutor wraps auctions behind a uniform interface, you can also use the Auction class directly for full control over bidding strategies, reserve prices, and round mechanics.
Auction Types
TnsAI supports four classic auction formats, each with different rules for bidding and pricing.
| Type | Mechanism | Winner Pays |
|---|---|---|
ENGLISH | Ascending price, bidders outbid each other | Their bid price |
DUTCH | Descending price, first to accept wins | Current (accepted) price |
SEALED_BID | Single sealed bid, highest wins | Their bid price |
VICKREY | Single sealed bid, highest wins | Second-highest price |
AuctionResult result = Auction.builder()
.type(Auction.Type.VICKREY)
.task("Translate document to French")
.bidders(List.of(translator1, translator2, translator3))
.bidStrategy((agent, task, currentPrice, round, previousBids) -> {
String response = agent.chat("Bid on: " + task + " (current: " + currentPrice + ")");
return Double.parseDouble(response.trim());
})
.startingPrice(100.0)
.reservePrice(20.0)
.increment(10.0) // price step for Dutch auction
.maxRounds(10)
.build()
.execute();
if (result.success()) {
System.out.println("Winner: " + result.winner().name());
System.out.println("Bid: " + result.winningBid().value());
System.out.println("Pays: " + result.payPrice()); // second-highest for Vickrey
}BidStrategy
The BidStrategy defines how each agent calculates its bid in each round. You implement this as a lambda or class that receives the agent, the task, the current price, the round number, and the history of previous bids.
@FunctionalInterface
public interface BidStrategy {
double calculateBid(Agent agent, String task, double currentPrice,
int round, List<Bid> previousBids);
}Return Double.NaN to drop out of the auction. In English auctions, returning a value less than or equal to currentPrice also drops the bidder.
Async Execution
Auctions can also run asynchronously so they don't block your main thread.
CompletableFuture<AuctionResult> future = auction.executeAsync();Judge Agent Pattern
TnsAI.Coordination provides a judge agent pattern for evaluating and selecting the best output from multiple agents. A judge applies a policy to score candidate outputs and pick a winner. Package: `com.tnsai.coordination.judge`.
Communication Protocols
TnsAI provides annotation-driven inter-agent communication through `ProtocolManager`. Annotate your agent class with one or more communication paradigms and the framework auto-configures handlers, transports, and discovery.