Inspiration

Most negotiation advice is theoretical — read a book, watch a video, hope you remember it when you're actually sitting across from someone asking for less than you deserve. We wanted something you could actually practice against: an AI that doesn't just play along and agree with you, but genuinely resists, uses real negotiation tactics, and only moves when you give it a reason to. The goal wasn't a chatbot that's nice to you — it was a sparring partner that makes you better by being difficult.

What it does

Counterpart puts you across the table from a resistant AI negotiator in one of four scenarios — salary negotiation, rent renewal, freelance contract pricing, or a vendor price dispute — or a scenario you create yourself, with your own persona, personality, and walk-away point.

The AI counterpart stays fully in character, remembers everything you've said, and only concedes ground in response to real leverage: market data, competing offers, demonstrated value. Emotional appeals and repeated pressure alone don't move it — which is the entire point.

While you negotiate, a live coaching sidebar reads the exchange in real time: it names the tactic the AI just used (anchoring, reciprocity pressure, reframing, and others) and classifies your last move — a good move, a weak anchor, an unearned concession, or pressure you successfully resisted — with a plain-language explanation for each.

When you end the session, you get a scored report: an overall grade, an anchoring quality score, your concession pace, every tactic you faced, and three specific takeaways grounded in what actually happened in your conversation — not generic negotiation advice.

How we built it

We built Counterpart entirely with Codex as the coding agent, phase by phase: scaffolding, the persona engine, the live negotiation loop, the tactic-recognition and coaching classifier, the end-of-session report, database-backed custom scenarios, and voice output.

The backend is FastAPI, calling the Groq API (Llama 3.3 70B) for the negotiation persona and the structured tactic/coaching classification — a single combined model call per turn returns both the in-character reply and the coaching data together, avoiding the latency and cost of two separate calls. Custom scenarios are stored in SQLite so they persist across restarts. The frontend is React, Vite, and Tailwind, with the AI's replies optionally read aloud through the browser's speech-synthesis API.

Every feature went through the same loop: write a scoped Codex prompt, get real output, then actually test it — run the servers, click through the UI, pull raw API responses from devtools — rather than trusting Codex's own summary of what it built. That discipline caught several real bugs that would have otherwise shipped silently.

Challenges we ran into

The hardest problem wasn't getting the AI to resist — it was getting it to resist for the right reasons. Early testing showed the persona would gradually concede price purely in response to repeated emotional appeals (financial hardship, health issues, family obligations) with zero actual negotiation leverage behind them — technically "holding firm" in spirit, but drifting in practice. We fixed this by explicitly instructing every persona to only move in response to verifiable leverage, then re-tested with live transcripts to confirm it actually held.

That fix surfaced a second problem: the coaching classifier that grades your moves was almost never rewarding genuinely strong ones. It could catch bad moves reliably, but a textbook anchor — a specific number backed by real market data and competing offers — was still coming back labeled "none." We had to explicitly teach the model that "no mistake" and "good move" aren't the same category, with real positive examples, not just a list of mistakes to avoid.

A third round of testing found the same gap on the other side: pressure tactics like "just meet me halfway" were sometimes causing real concessions that went completely unflagged. We added a dedicated case for this, plus a new label — "resisted pressure" — for when the AI is pushed with no real leverage and correctly holds its ground, which didn't have a home in the original label set at all.

We also hit a real infrastructure constraint: our original plan assumed OpenAI's GPT-5.6, but we didn't have API budget for it. We pivoted to Groq's free tier early, which shaped the whole project — including a deliberate choice to combine the reply and coaching classification into one API call per turn instead of two, to keep latency and cost down on a free-tier model.

We also explored full duplex voice negotiation late in the build. Voice output worked well with the browser's built-in speech synthesis. Voice input hit a wall we couldn't resolve in time — a persistent network-level error in the browser's speech-recognition API that we traced to a proxy configuration issue, not a bug in our code. Rather than ship something unreliable, we kept voice output and made voice input a clearly documented next step.

Built With

Share this project:

Updates