TnsAI
Coordination

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 NegotiationProtocol

Four 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.

StrategyClassBehavior
LinearLinearConcessionConstant concession rate per round. Concedes (current - reservation) / remainingRounds each round.
ExponentialExponentialConcessionConcedes slowly at first, accelerates near deadline.
Tit-for-TatTitForTatConcessionMirrors opponent's last concession amount. Cooperative if opponent cooperates.
Time-DependentTimeDependentConcessionConcession 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.

FieldTypeDescription
typeNegotiationTypeProtocol type (required)
maxRoundsintMaximum negotiation rounds (\>= 1)
timeoutDurationOverall negotiation timeout
reservationValuedoubleMinimum acceptable value (walk-away point)
initialOfferdoubleStarting offer value (-1 for auto)
concessionStrategyConcessionStrategyStrategy 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.

FieldTypeDescription
agreedbooleanWhether agreement was reached
acceptedOfferNegotiationOfferThe accepted offer (null if no agreement)
allOffersList<NegotiationOffer>All offers made during negotiation
roundsUsedintNumber of rounds conducted
durationMslongTotal negotiation duration
finalStateNegotiationStateFinal state: AGREED, FAILED, TIMEOUT, DEADLOCK
failureReasonStringReason 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.

TypeMechanismWinner Pays
ENGLISHAscending price, bidders outbid each otherTheir bid price
DUTCHDescending price, first to accept winsCurrent (accepted) price
SEALED_BIDSingle sealed bid, highest winsTheir bid price
VICKREYSingle sealed bid, highest winsSecond-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();

On this page