The Deal Service
services/deal.ts, step by step. AI sources, qualifies, books, meets and negotiates; a warm reply sends the booking to a setter; the deal's value, a buying committee or the seller's ask sends the meeting and everything after it to one closer. The gate holds the meeting to someone attending and the close to the seller's floor, and the closer works the account in the seller's name.
sales's wordsdeal is a jobaccount is a subjectgig is a taskcloser is a memberseller is a clientseller is a buyer
Each step is AI's until its escalate names a role, so a person books, meets or closes only where one pays off.
humans_from, setter and stall_days bind the mechanics, so the seller or its agent moves them. floor binds the merits: only the seller sets it.
cat services/deal.ts policy: {
floor: { word: 'number', default: 0.8, min: 0.1, max: 1, binds: 'merits', note: 'the least share of list a close may take' },
humans_from: { word: 'money', default: 50_000, note: 'a closer takes deals at or above this value' },
setter: { word: ['ai', 'on_warm', 'person'], default: 'on_warm', note: 'who books the meeting' },
stall_days: { word: 'number', default: job => (job.input.motion === 'B2C' ? 7 : 14), min: 3, max: 90 },An order past a bound hears every violation at once, with next.fill naming each field:
→ POST /deals {"account":{"name":"Northwind Freight"},"value":80000,"offer":"https://harbor.example/route-planner","policy":{"floor":1.2,"stall_days":2},"max_usd":12000,"key":"northwind-2"} "code": "policy_invalid",
"reason": "The policy breaks 2 bounds: floor must be at most 1; stall_days must be at least 3.",
"fill": ["policy.floor", "policy.stall_days"],| Step | Who | What moves it on |
|---|---|---|
source | AI | finds the contacts at the account |
qualify | AI | fit goes to book; no fit ends the deal lost, no_fit |
book | AI, or a setter on warm | a meeting on the account's calendar; the setter's gig stays open until it is held |
meet | AI, or a closer from humans_from, committee or person | a meeting the guard accepts as held, which pays the setter $250 |
propose, negotiate | AI, or the same closer | the proposal, then the terms and redlines, each committed at the gate; the gig stays claimed |
close | AI, or the same closer | a committed closed_value with the seller's invoice is won; none is lost, with its reason |
nurture | a nurturer, routed | gone_dark brings it here; revived returns to the step it left, dead ends it lost, dark |
A closer's span is one gig, from the meeting to the close, and only the close fulfills it:
cat services/deal.tsconst setter = job => job.policy.setter === 'person' || (job.policy.setter === 'on_warm' && job.signals.warm)
const person = job => job.input.value >= job.policy.humans_from || job.signals.committee || job.signals.person
closer: { pool: closers, span: ['meet', 'propose', 'negotiate', 'close'], revive: { first_call: '24h' },
signals: {
warm: job => job.record.replies > 0, committee: { inbound: true }, person: { inbound: true },
gone_dark: { silent: job => `${job.policy.stall_days}d`, of: 'account', goto: 'nurture' } },
book: { by: 'ai', escalate: { to: 'setter', when: setter } },
meet: { by: 'ai', escalate: { to: 'closer', when: person }, when: job => job.input.motion !== 'B2A',
propose: { by: 'ai' }, negotiate: { by: 'ai' },
nurture: { by: 'nurturer', routed: true, next: job => (job.output.revived ? job.record.interrupted : 'lost') },The setter is paid for the meeting, not the booking: the pay waits on meet.held, the guard that reads the calendar for someone from the account staying 10 minutes. A meeting nobody attended is refused, rebooked and paid once.
cat services/deal.ts setter: { pool: closers, pay: { usd: 250, on: 'meet.held' } },
guard: { held: (m, job) => m.attended(job.input.account) >= 10 || 'no one from the account stayed 10 minutes' } },talk puts the closer in the client's place: she writes from the seller's domain and calls from its number, so the account deals with the seller alone:
cat services/deal.ts talk: { as: 'client', mail: 'client_domain', calls: 'bridged' } },npx services.dev check --connections # every outside system the sales folder reads, and what stands in for it on localhostservices/deal.ts:31 roles.closer.talk.mail the seller's sending domain https://mail.example.test/sellers/{seller}
services/deal.ts:31 roles.closer.talk.calls the seller's calling number https://calls.example.test/bridgeEach touch renews the claim, and only the account's own reply resets gone_dark:
→ GET /gigs/tsk_7Hq5/thread # as mbr_7Hq4, on closers.deals touch 4 9 Nov 11:30 mail · dana@harbor.example confirms the meeting the setter booked
touch 5 10 Nov 11:00 meeting · bridged from Harbor's number · ops@northwind.example stayed 34 min · held
touch 6 11 Nov 16:00 mail · dana@harbor.example → ops@northwind.example · proposal-northwind.pdf
touch 7 13 Nov 10:15 reply · ops@northwind.example · "the CFO wants three years priced" · resets gone_dark
touch 8 18 Nov 14:10 mail · redlines on the MSA, a three-year term
touch 9 25 Nov 16:20 call · bridged · 31 min · they asked for 40% off
touch 10 3 Dec 09:45 reply · "approved: year one at $74,000, net 30"
hold yours while Northwind talks · released only by gone_dark, an end or giving it back
sent as Harbor Labs · mail from harbor.example, calls from Harbor's number · Northwind never sees closers.dealsclose.guard refuses a close below the floor, from AI or a closer alike. The terms and the floor freeze at the claim, and a refused close leaves the gig claimed:
cat services/deal.ts guard: { closed_value: (v, job) => v >= job.input.value * job.policy.floor || 'below the floor you set' } },→ POST /gigs/tsk_7Hq5/complete {"closed_value":50000,"notes":"they asked for 40% off for a three-year term"} "code": "guard_refused",
"reason": "closed_value: below the floor you set ($50,000 < $64,000). The gig stays yours: propose again, or give it back.",
"gig": { "id": "tsk_7Hq5", "status": "claimed", "hold": "renewed by each touch", "terms": "frozen at the claim, 9 Nov" },→ POST /gigs/tsk_7Hq5/complete {"closed_value":74000,"term":36,"contract":"msa-northwind.pdf","pay_link":"https://invoice.stripe.com/i/acct_…H4rb/in_…8841","notes":"three years, the first at list less 7.5%"}← 200 · tsk_7Hq5 · done · closer · close · committed · closed_value $74,000 · won · nothing is charged until Northwind pays in_…8841When the account's best is under the floor, the closer asks, and only the seller answers, for this deal alone:
→ POST /gigs/tsk_7Hu4/ask {"to":"floor","value":0.7,"why":"their budget is fixed at $58,000 for year one, on a three-year term"}← 202 · ask_7Hu4 · to Harbor Labs · set the floor on job_7Hu4 · the gig stays yours while they decide→ POST /portal/deals/job_7Hu4/policy {"floor":0.7,"for":"deal"} # Priya, in Harbor Labs' own session← 200 · job_7Hu4 · floor 0.7 · this deal only · $56,000 · your other deals keep 0.8→ POST /gigs/tsk_7Hu4/complete {"closed_value":58000,"term":36,"contract":"msa-coho.pdf","pay_link":"https://invoice.stripe.com/i/acct_…H4rb/in_…5806"}← 200 · tsk_7Hu4 · done · closer · close · committed · closed_value $58,000 · won · over the floor you set for this deal ($58,000 ≥ $56,000)won charges once the account's payment proves it. lost charges nothing and records one of five reasons, which the console counts without reading the deal. An end is forever, and again starts a new deal.
cat services/deal.ts contract: 'file?', pay_link: 'url', notes: 'text', lost: ['no_fit', 'price', 'timing', 'competitor', 'dark'] },
ends: { won: { charge: true }, lost: { charge: false, by: ['client', 'brand'] } },
outcome: { amount: 'closed_value', proof: 'payment', match: { customer: 'account', metadata: 'deal' },
window: { bank: '60d', card: '120d' }, tail: '90d', due: '90d', collect: job => (job.output.billing === 'monthly' ? 'each' : 'first') },
price: { usd: 0, share: '5%', of: 'closed_value', on: 'won', ai: 40,
lines: { referral: { share: '1%', of: 'closed_value', to: 'referrer', on: 'won' } } },The money follows a won deal's dollars. Policy, signals and ends cover each key for any Service.