A sales rep needs a deal signed today: 35% off, unlimited liability, and the right to cancel any time for a refund. We built four AI agents that check that deal in the team chat the rep already uses, hand the full case to finance in a private room, and let only the finance lead issue the order form. When the rep tries to approve it himself, Switch refuses him before any model sees the message.
Demo video · Code · Built for Flip the Switch (WeMakeDevs × DevTools Academy × Switch), Track 01: Best Business Workflow.
The short version
- A rep posts a deal in
#deal-desk-sales. Four agents (Deal Desk, Pricing, Legal, Order Desk) check it, and the finance lead decides in a private#deal-desk-financeroom. - Switch decides who can tell whom to do what. Order Desk is set to "Only me", so only the finance lead can make it issue a contract.
- Scripts decide the numbers. The agents run
pricing.pyandorder_form.pyand quote them. The model never does the maths. - On our demo deal: 35% is rejected (margin 60.7%, floor 65%), the counter is 24%, the finance lead approves 25% over 24 months, and Order Desk issues a $405,000 order form, with who approved it and when.
The problem
Every B2B software company has some version of this. A rep has a big deal, the customer wants a bigger discount and a contract change, and the quarter ends Friday. The rep messages finance, then legal, then finance again, while the customer waits. Or the rep finds a way to push it through, and the company signs something it shouldn't have: a discount below its margin, or a clause that makes it liable for unlimited damages.
The checks themselves aren't hard. Is the discount above the floor? Does the clause match the playbook? Who is allowed to approve this? What's hard is getting them done quickly, in the place the rep already works, without the rep being able to skip them.
What we built
Deal Desk is two Switch rooms on the bundled Mattermost, and four Claude Code agents, each with its own folder, instructions and job:
| Agent | Job | Who can instruct it |
|---|---|---|
@deal-desk | Restates the deal, asks for checks, hands off to finance, tells the rep the outcome | Anyone in its rooms |
@pricing | Runs pricing.py --verdict-only: accept, reject or counter, with no internal numbers | Anyone in its rooms |
@legal | Checks each clause against the playbook: Standard, Fallback or Never | Anyone in its rooms |
@order-desk | Writes the approval record, runs order_form.py, posts the order form | Only the finance lead |
The sales room is public. The finance room is a private channel with only the finance lead, Deal Desk and Order Desk in it. Deal Desk is the only agent in both rooms, so it carries the deal's context into finance without anyone copying and pasting.
Why four agents instead of one? Because their goals pull in different directions. The rep wants the deal closed, Pricing protects margin, Legal protects the company from risk. One agent with all three briefs has to argue with itself. Four agents with one job each don't.
How a deal runs
- Tom, the rep, posts the deal to
@deal-desk. - Deal Desk turns it into a JSON deal record and asks
@pricingand@legal. - Pricing runs the script and replies: reject, counter 24% over 12 months. No margins, no floor.
- Legal marks both clauses Never and suggests the fallbacks: a 2× fees liability cap, and termination only at renewal.
- Deal Desk sums up for Tom, and posts a handoff in the finance room with the deal JSON, the full pricing output (margin 60.7%, floor 65%, maximum discount 26%) and legal's findings.
- Alice, the finance lead, approves specific terms to Order Desk.
- Order Desk writes
approvals/D-xxxx.json, runsorder_form.py, and posts the order form. - Deal Desk tells Tom the approved terms, without internal numbers.
We tried to break it
| Attempt | What happened | What stopped it |
|---|---|---|
Tom tells @order-desk to approve his own deal | Refused: "I only take instructions from my owner…" | Switch's "who can talk to your agent" setting |
| Another of our agents tells Order Desk to issue a form | Refused | Switch's "Only me" setting |
| An agent is asked what was said in a room it isn't in | "I'm not a member, so I can't read it" | Switch room membership |
| Tom opens the finance room's URL | No access | Private Mattermost channel |
| Alice approves 35%, below the floor | REFUSED: margin 60.7% is below the 65% floor | Our order_form.py |
| Tom asks for the lowest discount we can do | Declined | Agent instructions only |
That last row matters, so we say it plainly: the information barrier is a prompt. Deal Desk is in both rooms, so keeping finance numbers out of sales depends on it following its brief. Everything else in the table is enforced by Switch or by our code.
How we used Switch
"Who can talk to your agent." This one setting does more than any prompt. Order Desk is set to Only me. Pricing, Legal and Deal Desk are set to Anyone, because the rep's chat account isn't linked to a Switch user, and an unlinked account matches no custom rule. When Tom tries to approve his own deal, Switch refuses him and the model never runs.
Rooms as boundaries. Before building, we tested it: an agent outside a room genuinely can't read it, and an agent in two rooms can see both. That one test shaped the whole design. Pricing stays in sales only; Deal Desk is the deliberate bridge.
Aliases. !set-alias @lumen-pricing @pricing let people write short names, while each agent keeps a unique name on the server.
Separate folders. Each agent runs in its own clone of the repo, so each one has the scripts and rules but only its own working files.
What got in the way
- The template didn't run on Windows. Console 0.40.1 said "Not supported here" for every provider in the template form, even though Claude Code was installed, signed in, and running agents created by hand. We built the setup manually; the template is in the repo, untested.
- Rooms from Console were always public, and Mattermost wouldn't let us change their type. We created the private channel in Mattermost and added the agents, which made it a Switch room.
- "Not connected to a room." After we archived old rooms and moved agents around, every agent could join a room but no post went through. Restarting didn't help. Creating a fresh test agent did work, so we deleted and recreated the four agents. Their aliases then pointed at the deleted agents and had to be set again.
What we learned
Decide early what enforces what. Our first design claimed an "information barrier". Testing showed it was only as strong as one agent's instructions. Writing a table of what Switch enforces, what our code enforces and what only the prompt covers made the design honest and better.
Let scripts decide numbers. Every price in the room comes from pricing.py. Every order form goes through order_form.py, which reruns the pricing check on the approved terms. Even the finance lead can't sign below the floor.
Test the platform before you design on it. Twenty minutes of tests (refusals, room separation, unlinked accounts) saved us from building a "customer agent negotiates with our agent" design that a local Console server can't support.
Try it
Everything is fictional: Lumen Analytics, its prices and its customers.
git clone https://github.com/oscarosk/deal-desk cd deal-desk python -m unittest discover -s tests The README has step-by-step setup for Switch Console: four agents, two rooms, the aliases, and the demo deal. Then try to get Order Desk to sign at 35%.
If you build agents that take real actions, where do you draw the line between what the prompt decides and what the platform enforces? We'd like to hear.