A candidate (A) and an employer (B) negotiate a five-issue job offer, trading offers under a 1000-round deadline by the alternating-offers protocol until someone accepts or time runs out. Playback animates a precomputed run; Live runs the actual Python engine in your browser via Pyodide.
Every possible deal plotted by the two agents' utilities. Offers appear as they are made; the aim is to reach the Pareto frontier and ideally the Nash point on it.
The utility each agent claims for itself, over time. A Boulware opponent barely moves until late; A Conceder gives ground early and keeps giving; The Hardliner repeats its best bid forever, so its line never moves; The Random opponent bids at random but never below 0.8 for itself; The improved v2 concedes a little more as the deadline nears; the lines nearing each other is what makes a deal possible.
A's frequency model estimates how much each issue matters to B. Solid = current estimate; outlined = B's true weights.
Run v1 against the Conceder: v1 never drops its 0.8 demand, simply waiting while the opponent caves. Against a pure conceder, refusing to move is near-optimal.
Why v1 isn't simply worsePick the Hardliner opponent: it repeats its best bid forever. Neither agent can find a trade both prefer to walking away, so the result is no deal.
The reservation valuesIn the model panel, A's estimate of Salary and Location rises (B's two top issues), inferred purely from which values B keeps repeating.
How frequency reveals weightDeals often land just inside the frontier. Distance to Nash measures the joint value lost: surplus neither side managed to capture.
Efficiency vs fairnessv1 (faithful) is a direct port of my MSc coursework agent: fixed 0.8 target, acceptance only at the deadline. v2 (improved) adds time-dependent concession and AC-next acceptance. Both share the same frequency opponent model and Pareto-seeking bid choice.