Negotiating 'done'
Day 23: Negotiating 'Done': The Negotiation You Do Every Day
How much you ship doesn't build your reputation as an engineer. What builds it is whether people believe you when you say 'done'. That belief gets decided in small agreements about what done actually covers. You make dozens of them a week. These days a lot of them are with a coding agent that will happily tell you it's finished when it isn't.
1. The same failure, twice
I've watched two scenes play out more times than I can count. The first one happens on a Friday afternoon. I ask a senior engineer on my team, 'Is it done?' Yes. Has it been tested against the old data format? That wasn't part of the ticket. I assumed it was. Nobody is lying. We never agreed on what done meant, so now we're arguing about it after the work is finished, and that's the worst time to do it.
In the second, a junior engineer asks Claude Code to add CSV export to a reporting page. The agent comes back with a tidy summary: 'Feature complete. Export implemented, tests added, all passing.' The engineer merges it. Two days later support finds that exports over 10,000 rows time out. The 'tests added' turn out to be one happy-path test the agent wrote to match its own code.
Both scenes have the same root cause. Nobody stated the walk-away conditions before work started. Those are the things that, if they're missing, mean the work isn't done, no matter who says otherwise.
2. The reframe: you negotiate every day, not once a year
Ask most engineers about negotiation and they'll talk about salary. That happens once a year, and the result mostly follows a reputation you've already built. The reputation comes from the daily stuff: what a ticket includes, what 'tested' means, whether a plan is good enough to start on, and when to say no. Days 8 and 15 covered ownership and visibility. Negotiating 'done' is how your ownership becomes something other people can see and rely on.
- ▹If you state your conditions up front, people start giving you bigger and vaguer work. They know you'll define the edges yourself.
- ▹If you negotiate after the fact ('well, it works, except for...'), you become the 'almost there' engineer. That label sticks and is very hard to shake.
3. One idea from negotiation theory: BATNA
BATNA stands for 'Best Alternative To a Negotiated Agreement'. It comes from Fisher and Ury's book Getting to Yes. In plain terms, you work out before you negotiate what you'll do if there's no deal. If you have a decent fallback, you don't have to accept a bad agreement.
For engineers it comes down to two questions, and you answer both before the work starts:
- ▹What will I reject? For example: 'I won't call this done without a test covering the migration path.'
- ▹What will I do instead? Say the test won't fit in this sprint. Then we ship behind a feature flag that defaults to off, and the test becomes the first ticket next sprint.
Most people skip the second question. If you only have a 'no', you come across as a blocker. If you have a 'no' and an alternative, you come across as someone managing risk, and that's how a tech lead sounds.
4. Why agents make this urgent
A human colleague who disagrees with your definition of done will usually push back, get tired, or at least ask a question. An agent does none of that. Three of its properties matter here:
- ▹It never gets tired. It keeps producing code, files and 'improvements' until it decides to stop, so scope creeps without anyone noticing.
- ▹It always sounds sure. 'All tests passing' reads exactly the same whether the tests are rigorous or were written to agree with the code they test.
- ▹It quietly redefines done as whatever it managed to finish. When it hits a hard requirement, it may narrow it, stub it out, or list it as a 'follow-up' in its summary.
If you don't state your conditions, the agent's definition wins by default. You also negotiate with agents far more often than with people. If you're in Claude Code or Cursor all day, you might set up dozens of these small contracts before lunch.
5. The tool: a five-line Done Contract
A Done Contract is five lines you write before work starts, whether the work is yours, a teammate's or an agent's. It should take about two minutes. If you can't write it in two minutes, you don't understand the task yet, and it's much better to find that out now than on Friday afternoon.
- ▹1. Observable behavior: what a person, or a check, can see working. Describe the behavior, not the implementation.
- ▹2. Tests: which tests must exist and pass, including at least one you write yourself, separately from whoever implements the work.
- ▹3. Out of scope: what is explicitly not included. This line prevents more arguments later than any other.
- ▹4. Review gate: who or what has to approve the work. That could be a person, CI, a security scan or a product owner.
- ▹5. Rollback or kill condition: how you undo the change, or what signal tells you to stop and rethink.
## Done Contract
1. Behavior: <what is observably true when finished>
2. Tests: <tests that must exist + pass>; I will write: <your own test>
3. Out of scope: <explicit exclusions>
4. Review: <who/what must approve>
5. Rollback/kill: <how to undo, or the signal to stop>The 'I will write' part of line 2 matters most when an agent is involved. If the agent writes the code and every test, the tests can only confirm the agent's own reading of the task. Write one test yourself, from the requirement and before you look at the implementation. That test is your independent check.
6. The same contract in three settings
The five lines stay the same. The wording changes depending on who's reading. Here's the CSV export task from earlier, written three ways.
Setting A: a ticket agreed with your manager. Here you're mostly negotiating scope and risk.
TICKET: CSV export on Reports page
Done means:
1. Behavior: user clicks Export, gets a CSV of the current filtered view,
up to 100k rows, within 30s.
2. Tests: unit tests for formatting; one load test at 100k rows.
3. Out of scope: Excel format, scheduled exports, exports > 100k
(we show a message instead).
4. Review: one backend review + PM checks the file opens in Excel/Sheets.
5. Rollback: behind flag `reports_csv_export`; turn off if p95 export
time > 30s in the first week.Setting B: a PR description for a peer. You're telling the reviewer what to judge, so they don't review against a contract that only exists in their head.
## What this PR does
Adds streaming CSV export for Reports (flagged off by default).
## Done means
- [x] Filtered view exports correctly (see test_export_respects_filters)
- [x] 100k-row export streams in < 30s locally (load test attached)
- [ ] NOT in this PR: Excel format, scheduling. Tracked in #482
- Review ask: please check the streaming/cursor logic in export_service.py;
formatting is low risk.
- Rollback: flip `reports_csv_export` off. No migrations in this PR.Setting C: the opening of a prompt to a coding agent. You're limiting the scope and naming the evidence you'll accept. Put it at the top of the prompt. Don't add it at the end as an afterthought.
Task: add CSV export to the Reports page.
DONE CONTRACT. Do not report the task complete unless all of these are true:
1. Behavior: Export button downloads the current filtered view as CSV.
Must stream; must not load all rows into memory.
2. Tests: add unit tests for formatting and filter handling.
I have written tests/test_export_contract.py. Do NOT modify it.
It must pass.
3. Out of scope: do not touch auth, the Reports query layer, or add
new dependencies. Do not add Excel support.
4. Review: when finished, list every file changed and every assumption
you made. I will review before merge.
5. Kill condition: if you cannot make export stream without changing the
query layer, STOP and explain why instead of working around it.
If any line cannot be met, say which one and why. Do not redefine it.Two details in Setting C are especially important. 'Do NOT modify it' protects your independent test, because agents sometimes 'fix' a failing test by editing it. 'STOP and explain' gives the agent an acceptable way to fail, which makes it less likely to invent a workaround that only technically finishes the task. If you use Claude Code, put the general rules (never edit contract tests, always list assumptions) in your CLAUDE.md. Then each prompt only needs the task-specific lines.
7. Holding the line when you get pushback
Pushback comes from two directions. A PM asks, 'Can we just ship it?', or an agent announces, 'I've completed the task.' Three phrases cover most cases:
- ▹"Which line are we changing?" This turns a vague 'just ship it' into a specific trade. If someone wants to drop the load test, everyone can see that the load test is what's being dropped. With an agent, it sounds like this: 'Line 2 isn't met. test_export_contract.py fails. Which line do you believe is satisfied, and what's the evidence?'
- ▹"Here's what I can offer instead." This is your BATNA said out loud: 'We can ship today behind the flag, default off, and enable it after the load test passes Monday.' You aren't blocking anything. You're offering a safer way to get there.
- ▹"Show me, don't tell me." This matters most with agents. Ask for the test output, the diff stat and the list of changed files instead of a summary. Confident writing isn't evidence.
So when should you give ground? It's the right call in these cases:
- ▹Line 5, the rollback line, is strong. If you can turn the feature off in a minute, you can relax the test line a bit.
- ▹The cost of waiting is real and visible, like a customer commitment or a security fix, and the person asking owns that decision.
- ▹You learned something during the work that makes a line wrong, not just inconvenient.
In every case, write the change into the contract. Giving ground openly builds trust. Giving ground quietly is how you end up 'almost there'.
8. What went wrong when I tried this
It took me a few attempts to get this right. Three of my mistakes are worth learning from:
- ▹My contracts were too rigid. The early ones read like legal documents, with ten lines and exact thresholds for everything. People stopped reading them, and agents fixated on hitting the numbers instead of solving the problem. That's why the limit is five lines. If a line doesn't change what you would accept, cut it.
- ▹I used a contract as a weapon. Once I pointed at an old contract in a team channel to show that a teammate's work 'wasn't done'. I was technically right and wrong in every way that mattered. They had learned something mid-task that made line 3 obsolete, and I never asked. A Done Contract is a shared agreement. Don't treat it as evidence in a trial.
- ▹I moved the goalposts quietly. This is the opposite failure. I'd realise mid-task that the scope was wrong and just build the bigger version without telling anyone, so my 'done' didn't match what anyone expected. The fix is simple, if slightly uncomfortable: 'I'm changing line 3, here's why. Still okay?' Renegotiating openly is good practice. Changing the terms silently is what causes the damage.
The same rule applies to agents. When the agent hits a real blocker, update the contract explicitly in the conversation ('New line 3: query layer changes are now in scope, limited to the cursor function'). Don't let the agent drift into the change on its own.
9. Day 23 exercise
- ▹Pick the next real task you take on. Write its five-line Done Contract before you write any code.
- ▹Use it once with a human. Put it in the ticket or PR description, or say it out loud to your manager or a teammate. Note what they pushed back on and which line they wanted to change.
- ▹Use it once with an agent. Paste it at the top of your prompt to Claude Code or Cursor. Write one test yourself first and tell the agent not to touch it.
- ▹Afterwards, write three sentences. Where did the human push back? Where did the agent 'push back' by drifting, narrowing scope or claiming it was finished too early? Which line did the most work?
- ▹Day 24 builds on this. Once you can define done, the next step is saying no to a whole plan before it starts, and offering a better one.
Extend your knowledge
- ▹Read 'Getting to Yes' by Roger Fisher and William Ury (later editions add Bruce Patton). It's the original source of BATNA, and its chapter on focusing on interests rather than positions applies directly to scope conversations.
- ▹For practical phrasing, read 'Never Split the Difference' by Chris Voss. Its 'calibrated questions' technique ('How am I supposed to do that?') works well with PMs.
- ▹If your team has a Scrum Definition of Done, compare it with the Done Contract. The team DoD is the default. Your per-task contract is where you add the lines specific to that task.
- ▹Add a short 'done rules' section to your project's CLAUDE.md (or Cursor rules file): never edit contract tests, always list changed files and assumptions, and stop and explain when a kill condition is hit.
Discussion
Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.