Parley

Submission · architecture

Money, time, protocol:
code.

Deterministic code owns every number, every deadline and every API call; templates write the e-mails. The Live Final build (v1.38) runs without a language model, because on paired games the model layer did not move the score.

Data flow per case

  1. Dataparley data

    Tacto's data package and the Sunday release, checksummed, plus the case file: need, goal, must-haves, alternative.

  2. Dossier + profilesdossier.py

    Every fact Parley may cite, with the data ID the arena checks, and a supplier profile prepared offline with its evidence quotes.

  3. Valuationvaluation.py

    Total cost C over the contract for every standing and possible sheet, per lever, including the supplier-health charge, the index clause and a cost of money per supplier.

  4. Policy + guardspolicy.py · rules.py

    Anchor, concession steps, packages that cost us the same, one new fact per round, accept or walk. Guards G1–G9 fence every move.

  5. Mail templateswriter.py

    Templates write the words; code fills in every figure, data ID and sheet.

  6. Arena clientrunner.py

    Brings the quotes and every reply. Sends with a move ID that wait up to 180 s, 409 pickup, 429 backoff, one worker per supplier thread, a journal entry per decision.

  7. Reader ↺ loopReply → reader → valuation → policy, every round, until Parley accepts or walks (sent through the arena client). Outside the loop: the profiles were prepared offline, the mail lint runs in our tests, and each decision's reason in the journal feeds the replay.

Modules

  1. 01Dossierdeterministic code

    Built per supplier before the first e-mail, from the data package (orders, invoices, scorecards, contracts, letters, call notes, news, market indices) and the Sunday release. Every fact carries its data ID, checked against facts.json before it goes into a mail. Release items are read the same way: inputs “bought forward” count as hedged, a lost customer in the news as free capacity. If the dossier cannot be built, it is retried once and the case plays on a plain profile (G9).

  2. 02Supplier profilesverified offline data

    Prepared once, offline, from Tacto's data package only. The profiles give patience, levers and the cash and hedge signals the policy uses; a cash or hedge flag is kept only if its evidence quote is found in the data. 17 profile fields per detailed supplier (approval threshold, levers, capacity, price freeze, cash need, hedged inputs, who signs) carry a source ID and a verbatim quote: no quote, no value. A second model family cross-checked all 46 suppliers, a third 10. Most field-based switches are off in the final because they did not pass our gate.

  3. 03Valuationdeterministic code

    The arena's published cost formula, with our estimates for what it does not show: price × volume, payment interest, freight by Incoterm, late lead time, expected defects minus chargebacks, tooling, uncovered need and fallback risk, index clause path and risk, and on relationship goals the supplier-health charge (supplier full cost estimated as should-cost / 1.02; it matched the arena within 1 % on our own sessions). It also prices what a package is worth to the supplier, with a cost of money from documented evidence and the payment terms it picks; stock phrases do not count.

  4. 04Policydeterministic code

    Opening anchor (deeper for leverage and RFQs, never below the estimated floor; on relationship goals a further 3 % of the opening price lower). Steps react to the supplier: less after a big step, almost nothing after an announced internal step, more when it holds. Sheets in one mail cost us the same and differ in one lever (three in round 1, two later, one when closing), so the supplier's choice shows what it values. Two facts in round 1, then one new fact per round. The close takes the cheapest standing offer that keeps every must-have and beats the alternative, or walks.

  5. 05Reader + writerdeterministic code

    Rules read each reply: status, claim verdicts, signal sentences. Templates pick an opening for the situation (price letter, renewal, RFQ, capacity, quality), never repeat within a thread and name the concern the supplier raised. An optional model layer exists (--mode hybrid reads, --mode full also writes one paragraph without digits). The final runs --no-llm: on 90 paired games the layer moved the score by +0.0001, while a decision took 7.4 s instead of 0.2 s (p95).

  6. 06Runner + supervisordeterministic code

    Plays every case of a wave in parallel, one worker per supplier thread, and closes about 90 s before the 8-minute cap. Restarted after a crash, it picks up its open sessions through the 409. The supervisor restarts it after 3 s and, after three exits in 120 s, switches to the next build of the fallback chain.

Guards G1–G9, pinned by tests

  • G1No counter-offer after a final offer: mirror the supplier's own cheaper alternative or end the thread; the close accepts or walks.
  • G2RFQ: a clearly cheapest bid within 3 % of our ask, or an agreeing leader, closes the case at once.
  • G3Acknowledge only a concern the supplier raised (cash, margin).
  • G4No index clause as a lever toward a hedged supplier, unless it asked for one.
  • G5Where the arena charges supplier health, packages start from the estimated health floor.
  • G6In a thread, a raised cost of money needs a real cash statement or a chosen short-payment sheet.
  • G7Retries reuse the move ID; a timeout or 409 re-reads the arena before deciding again.
  • G8The first ask never goes deeper than the depth we tested.
  • G9Dossier before the first move; on failure, retry once, then a plain profile.

Acceptance checks on top: an offer is taken only if it keeps every must-have and costs less than the opening and the alternative; an unreadable quote PDF cannot satisfy an open must-have; and a deal with a low estimated score is not closed while moves, a fresh fact or an unused lever are left.

RFQs and time

RFQ

Each bidder in its own thread. First moves are decided in a fixed supplier order before any is sent. From round 1 the cheapest rival price goes to every dearer bidder as a checked claim; bidders more than 8 % behind are parked; a clear best bid within 3 % of our ask is taken, and the case closes at once.

Time

60 s per message, 8-minute waves. The runner begins closing about 90 s before the cap, on the best standing offer. Pacing counts from measured round trips how many rounds still fit; only when time binds does the policy take bigger steps, and a mail whose reply would land after the close is not sent.

Parameters of the final build

Set by hand, then tested on our own persona bank (paired, same cases and seeds) and, where noted, on practice cases. For the swept ones, no other value met our rule for a change (+0.01 in two runs, no persona down by more than 0.02, no new zero), so they stayed.

  • First-ask depth6–16 % per goal, RFQ +8, max 22 % (RFQ 26 %)

    Where round 1 opens below the supplier's quote.

    Tested · Deeper anchors across the board lost on practice cases; only the relationship rule passed.

  • Relationship depth rule+3 points

    Stabilise and secure-supply goals ask deeper; suppliers often agreed to the old first ask at once.

    Tested · Paired bank runs; off in the safe fallback build.

  • Should-cost clamp0.94

    Never ask below 94 % of our estimate of the supplier's cost.

    Tested · Swept 0.92–0.98: 0.98 cost −0.016, 0.92 gained +0.004; kept.

  • Reciprocity0.6

    Our step = 0.6 × the supplier's step, as a share of the gap.

    Tested · Swept 0.4–1.0: −0.004 … +0.007, never +0.01; kept.

  • Step limits5–25 % of the gap

    15 % after the supplier closed over 40 % of the gap; at least 15 % toward a supplier that keeps moving.

    Tested · 25 % cap swept: flat (±0.002); mover floor +0.003 in two paired runs.

  • Price-increase step≥ 20 % of the gap

    Keeps price-increase cases from stalling once the supplier moves.

    Tested · Gated with the price-increase rules on bank and sandbox.

  • Facts in round 12, then 1 per round

    How much evidence the first mail carries.

    Tested · Swept 1–3: 2 is best (1: −0.005, 3: −0.003).

  • Cost of money6.5 / 9.5 / 14 %, in thread 10 %

    How much a supplier values faster payment: corporate, family-owned, documented cash need; 10 % after a real cash statement or a chosen short-payment sheet.

    Tested · Swept 0.05–0.25; 0.10 adopted (bank +0.006, practice P04 +0.058).

  • RFQ takewithin 3 %, lead > 5 %

    A clearly cheapest bid near our ask is taken and the case closes; bidders over 8 % behind are parked.

    Tested · Bank +0.0006 / +0.0037, no new zero; practice P05 0.659 → 0.865.

  • Round-1 rival claimon

    The cheapest rival price goes to every dearer bidder from round 1.

    Tested · Alone it lost on practice (a bidder walked); with the take rule it passed.

  • Index clausepath capped at ±8 %

    The clause is priced in C as the arena prices it. A 1.2 × margin rule and a wider deadband as a lever exist behind a switch, off.

    Tested · The switch gained +0.001 on the bank but lost on hold-out practice cases.

  • Send timeout180 s

    How long a send waits for the supplier's reply, per attempt.

    Tested · Live replies p90 61–74 s, max ≈ 100 s; 60 s made the client retry moves still being answered.

  • Close margin90 s

    The runner starts closing this long before the wave cap.

    Tested · Fault rehearsal and a five-wave load test: no case open at the cap.

Fallback chain

  1. rcrelease candidate
  2. safesame checkout, recent switches reset
  3. F1Rolder tagged build
  4. F1oldest tagged build

Three exits within 120 s switch to the next build. Each build runs from its own clean checkout with a locked environment.

Integration: Tacto Negotiation Arena API

GET /v1/cases?set=…
case files: need, goal, rates, must-haves, alternative, suppliers
POST /v1/sessions
open a negotiation; round 0 brings every supplier's quote e-mail
GET …/attachments/{id}
quote PDFs and documents, parsed for standard terms
POST …/threads/{supplier}/messages
our e-mail: text, up to three term sheets, data IDs; or accept / walk
GET …/threads/{supplier}
re-sync a thread after a timeout or a 409
GET /v1/sessions/{sid}
result, trace and replay link after the end

Sends

Every POST carries an X-Move-Id; HTTP retries of one send reuse it, so the arena sees one move. A send waits up to 180 s per attempt, bounded by the cap.

409 / timeout

After a timeout re-read the thread, after a 409 the session and the thread, then decide again from what the arena has. This is also how a restarted runner picks up.

429 / 5xx

A short 429 is waited out (Retry-After) and retried; the close is retried with backoff until shortly before the cap.

Live Final

No human in the loop; the agent never reads the event feed, screen, leaderboard or results.

How we test (our own tests)

Persona bank

9 modelled supplier behaviours × 20 generated held-out cases, scored with the supplier-health charge. A change goes in only if the bank is not lower, no persona drops by more than 0.02, no new zeros, sandbox not lower.

LLM-voiced suppliers

The same bank with Claude, Gemini, GPT and DeepSeek models writing the supplier side: ≈ 0.69–0.71 whichever family plays.

Strength guards

Our best own practice negotiations must stay reachable: each buyer turn is decided again with the build under test; a worse sheet blocks the merge.

Sandbox + practice

The arena's free cases three times per variant; the ten hosted practice cases once per release candidate.

Mail lint

Every buyer mail of a bank run: banned phrases, repeated openings, missing contact name, statements the supplier's mail does not back.

Overfitting guard

A test fails if the agent's code names a case, supplier ID or supplier name; three practice cases are hold-outs.

Operations

Pre-flight

Keys, kit, install, tests, arena key, clock, disk, power and network as PASS / WARN / FAIL before anything plays.

Fault rehearsal

The final simulated with the real supervisor: runner killed mid-wave, crash loop, 60–120 s supplier delays, 429 bursts, missing release. Pickup 1.7–5.0 s after a kill.

Export

The public repo is an allowlisted copy of one commit, scanned for secrets and private paths.

Stack

Agent

Python 3.12+, httpx, the arena's published cost function (vendored from the MIT starter kit).

Models

Not in the final's loop. Offline only: supplier profiles and profile fields, cross-checked by other model families.

Web

Next.js 16 + FastAPI behind one domain: this site, the deck and the replay viewer.

Voices

ElevenLabs text-to-dialogue for the replays; portraits are SVG drawn from data, no image model.