← Building a solo company with AI agents

Part 8 of 40 · 7 min read

Lead to cash: make the commercial commitment explicit

Experimental · Work in progress · Internal R&D

Turn a qualified lead into explicit scope, a reviewed quote and an accepted commitment that delivery can act on.

Arunank Sharan · Kuzushi Labs ·

In this article

A customer says, “This looks good; can you get started next week?” I still need to know what “this” includes, which price was accepted, what access is required and how the work will be paid for. A sales agent that marks the deal won before those questions are resolved can create work the company never meant to promise.

The Lead-to-Cash prototype provides an opportunity lifecycle, a scoring adapter, governed stage changes and a minimal payment-recording path. It does not yet implement a complete quoting, contracting and invoicing system. I use that distinction to decide what to automate next.

The existing opportunity path

IDENTIFIED → QUALIFIED → CPQ_IN_PROGRESS
  → PROPOSAL_SUBMITTED → NEGOTIATION
  → VERBAL_COMMIT → CLOSED_WON

The prototype has different authority treatment for early stages and closing. A sales scorer can add an estimate; a principal with the relevant commercial role advances the opportunity. An agent attempting an approval-gated close is routed to review.

The local helper makes the intended journey easy to exercise:

from l2c.pipeline import build_l2c

sales, dispatcher = build_l2c(runtime=shared_runtime)
sales.score(opportunity_id, tenant_id="studio-demo")
# advance_to_won walks fixture stages using explicit human-role principals.
# A live application must obtain those decisions from authenticated people.

The helper's ability to construct a human-role principal is a test convenience. It is not evidence of a real customer's acceptance or an authenticated founder approval.

A quote needs a machine-readable commitment

For the document-search pilot I would record the commercial terms separately from the prose proposal:

{
  "quote_id": "quote-017",
  "version": 3,
  "currency": "SGD",
  "fixed_fee": "6000.00",
  "deliverables": ["search interface", "source-linked answers", "handover"],
  "excluded": ["24-hour support", "unbounded document ingestion"],
  "acceptance": "agreed evaluation set reviewed with customer",
  "billing_milestones": ["deposit", "accepted handover"]
}

This is a design example, not a current schema promise. The quote should link to scope evidence, accepted exceptions, expiry and authorised versions. The human-readable proposal and the structured commitment must be generated from the same accepted data; otherwise sales and delivery end up operating different agreements.

The most useful agent work happens before approval

The agent can extract unresolved requirements from a meeting, compare them with the standard offering, draft a proposal and surface exceptions. It can identify that the customer requested an on-premises deployment while the quoted package assumes a managed service. That is a concrete review item.

The founder should see a small decision packet: requested work, standard scope, deviations, cost assumptions and the exact commitment that would be made. “Approve this quote” without the changed terms is not enough.

I would keep discount arithmetic deterministic and model output descriptive. The model can explain why an exception might help the relationship; a versioned policy determines which discount requires approval.

A won deal creates downstream obligations

The first handoff is to delivery: accepted scope, due dates, prerequisites, evidence needed for acceptance and billing milestones. A second handoff is to finance, but a sales win alone is not universal evidence that revenue has been earned. The prototype's simple financial-event examples need an explicit recognition policy before use with real books.

The cross-loop demonstration already keeps the opportunity connected to its source lead and campaign. That makes it possible to learn which enquiries produced work. The next contract should also establish one stable commitment ID shared by delivery and billing so they cannot each create duplicate obligations from the same deal.

Cases that should interrupt the happy path

The review queue should catch expired quotes, changed scope after approval, conflicting currencies, missing acceptance evidence, unusual liability terms and a request to start before prerequisites are met. A close-lost decision should preserve its reason; it may indicate a poor offer, wrong audience or unaffordable delivery model.

For one founder, the value is not removing negotiation. It is arriving at negotiation with complete context, and leaving with a commitment the rest of the platform can honour. I would measure time to a reviewable quote, exception frequency, scope discrepancies at handover and unauthorised commitments. Those measures connect the assistant's output to the business work it is supposed to improve.

Make acceptance a versioned fact

A customer's “yes” can refer to an earlier price, an attached proposal or a revised scope discussed on a call. The sales loop needs to resolve which commitment was accepted before closing the deal. I would record the accepted quote version and source evidence explicitly, rather than infer it from the latest message's positive sentiment.

from datetime import date
from company_lab.business import Quote, accept_quote, Review

quote = Quote(
    version=3, approved_version=3, amount_minor=600000,
    currency="SGD", expires=date(2026, 10, 1),
)
accepted = accept_quote(quote, date(2026, 9, 20), "customer-email-017")
assert accepted["amount_minor"] == 600000
assert accepted["acceptance_ref"] == "customer-email-017"

The example verifies a local rule; it does not interpret whether an arbitrary email forms a legal agreement. In the product, the founder's commercial workflow defines the accepted evidence and exceptions. The model's useful job is to find the relevant message, identify ambiguity and prepare the decision.

Split pricing calculation from proposal language

For the studio pilot, calculate fee, currency, milestone amounts and accepted exclusions before generating prose. A proposal renderer can then use those fields without allowing the language model to improvise a new price. If a discount is requested, create a proposed term change with a reason and review state.

An example pricing function can remain small:

def discounted_fee(base_minor, discount_bps):
    if type(base_minor) is not int or base_minor <= 0:
        raise ValueError("positive integer fee required")
    if type(discount_bps) is not int or not 0 <= discount_bps <= 1000:
        raise ValueError("this example allows at most a 10% proposed discount")
    return (base_minor * (10000 - discount_bps) + 5000) // 10000

assert discounted_fee(600000, 500) == 570000

The rounding rule is explicit and the example currency uses minor units. A real quoting engine also needs taxes, multiple line items, currency-specific precision and any pricing-policy exceptions. The calculated proposal remains a draft until the authorised decision is recorded.

Structured pricing and scope produce a quote version; accepted evidence creates delivery and billing obligations.

Connect the document, CRM and calendar without creating three truths

The document store owns a rendered proposal artifact with a content hash. The CRM holds a projection of the opportunity stage and commercial summary. The calendar holds the agreed review meeting. The platform's commitment record links all three and records which fields each integration owns.

If the proposal document changes externally, do not silently update the accepted amount from its latest text. Fetch its new revision, compare it with the approved structured terms and create a discrepancy. If the CRM stage becomes closed-won manually, reconcile it against acceptance evidence before launching delivery.

For a meeting booking, store the calendar event ID and customer timezone. Rescheduling must update the same event or explicitly cancel and replace it under policy. An agent promising “next Thursday” without resolving date and timezone has not completed scheduling.

The handoff packet should be useful without the sales conversation

Delivery needs the exact deliverables, exclusions, dependencies, customer owner, acceptance method, deadline and billing milestones. It also needs the unresolved risks that the founder knowingly accepted. It does not need every speculative message from an early sales call.

I would create the delivery request transactionally with the accepted commitment event. The receiving loop deduplicates by commitment and handoff type. A duplicate CRM webhook must not create another work order. If the customer later requests an additional language, the original obligation stays intact while a change request is negotiated.

The practical test set includes acceptance of an expired quote, a reply to the wrong revision, a discount exceeding the example limit, missing currency and a CRM update arriving before the signed document. The correct output can be a review task. What matters is that no downstream work is started on an ambiguous commercial promise.