by Intelliverse X

DFY Vending — Portfolio Autopilot

Done-For-You vending: an operator goes from "I want a machine placed" to an installed, selling machine — with specialized agents doing the legwork end to end and the operator confirming only at the human checkpoints. With autopilot on, the pitch is literal: all you do is buy and start negotiations; AI agents do everything else; all you see is revenue.

Backend modules: app/dfy.py (stage machine + integrations), app/dfy_negotiation.py (the negotiation engine), app/dfy_graph.py (negotiation graphs + learning), app/dfy_autopilot.py (autopilot, checkpoints, install team, auto-inventory, portfolio view). Routes: app/routes/dfy.py under /api/v1/dfy. Tests: tests/test_dfy.py.

The pipeline

Stages are strictly forward-only (_advance refuses skips and backward moves; corrections happen by cancelling):

pre_ordered → shortlisting → outreach → lease_chat → lease_sent → signed
            → ready_to_ship → shipped → installing → live
Stage What happens Automated by
pre_ordered Stage-0 entry: HotBox pre-order paid up front (full price, refundable until shipped) Stripe Checkout (TEST)
shortlisting Location Scout research → ranked candidates Scout agent (app/location_scout.py)
outreach Sequenced voice + SMS + email touches, then negotiation Fonoster voice agent + Telnyx SMS + Notifuse email
lease_chat Terms finalized — conversationally or via negotiation hand-off LiteLLM + deterministic grammar
lease_sent Placement agreement out for eSignature Documenso (template prefill)
signed Signed, machine not yet paid — (money checkpoint)
ready_to_ship Signed AND paid; fleet pin pre-provisioned at the leased coordinates auto
shipped Tracking ref recorded (autopilot auto-dispatches in sandbox) auto
installing Setup wizard: link serial, Nayax bind, planogram, variant checks operator OR install team
live Verified online + payments bound + planogram loaded; auto-onboarded auto

Generated stage graph (checkpoint-gated edges dashed)

Generated from the live transition tables by app/graph_spec.py — do not edit; run python scripts/gen_state_machine_docs.py to regenerate (CI enforces freshness).

flowchart LR START(("•")) --> shortlisting pre_ordered["Pre-ordered"] shortlisting["Shortlisting"] outreach["Outreach"] lease_chat["Lease chat"] lease_sent["Sent"] signed["Signed"] ready_to_ship["Ready to ship"] shipped["Shipped"] installing["Installing"] live["Live"] installing --> live lease_chat -. "checkpoint: lease_send + terms_approval" .-> lease_sent lease_sent --> ready_to_ship lease_sent --> signed outreach -. "checkpoint: negotiation_review" .-> lease_chat pre_ordered --> outreach pre_ordered --> shortlisting ready_to_ship --> shipped shipped --> installing shortlisting -. "checkpoint: scout_outreach" .-> outreach shortlisting --> pre_ordered signed -. "checkpoint: money" .-> ready_to_ship cp_call_outcome{{"call_outcome"}}:::checkpoint -.- outreach cp_contact_verify{{"contact_verify"}}:::checkpoint -.- outreach cp_mode_resume{{"mode_resume"}}:::checkpoint -.- outreach cp_negotiation_target{{"negotiation_target"}}:::checkpoint -.- outreach cp_reply_review{{"reply_review"}}:::checkpoint -.- outreach cp_nayax_fail{{"nayax_fail"}}:::checkpoint -.- live classDef checkpoint fill:#78350f,stroke:#f59e0b,color:#fde68a,stroke-dasharray:4 3; linkStyle 2 stroke:#f59e0b,stroke-dasharray:6 4; linkStyle 5 stroke:#f59e0b,stroke-dasharray:6 4; linkStyle 10 stroke:#f59e0b,stroke-dasharray:6 4; linkStyle 12 stroke:#f59e0b,stroke-dasharray:6 4;

Served live at GET /api/v1/meta/state-machines/dfy_deal. Step replay of any deal's timeline (state snapshot at every event, diffs, checkpoint jumps): GET /api/v1/dfy/deals/{dealId}/replay.

Machine catalog (server-side truth)

GET /api/v1/dfy/catalog — HotBox Air ($3,000), HotBox Pro ($5,000), HotBox Pro Max ($7,000). Checkout amounts always come from this catalog, never from the client. Hardware variants: reyeah, zhzn — the install wizard branches the dispense test (Reyeah motor test vs ZHZN queue-dispatch ack) and both run the same registration/Nayax/planogram verification.

Outreach — tri-channel (voice, SMS, email)

Every counterparty-facing message renders from ONE versioned, stage-aware script library (app/dfy_scripts.py, SCRIPT_VERSION lands on the deal timeline with every touch): first_touch, follow_up, negotiation_counter, negotiation_hold, terms_confirmation, lease_send_notice. Stage selection (stage_for) reads live deal + negotiation state; facts (deal_facts) interpolate the venue, contact, operator display name, machine specs, cited revenue estimate (always framed as an estimate, never a guarantee) and the negotiation's current offer / agreed terms. Envelope safety is structural: the fact dict has a frozen key set (ENVELOPE_SAFE_FACT_KEYS) that cannot carry the negotiation envelope, so no template can leak a bound on any channel.

The sequencer picks the channel per touch by operator preference then availability: channelOrder (portfolio setting, per-deal override on POST .../outreach/start) rotates round-robin across channels that have contact info, config, and no opt-out; every touch records channel, stage and script version on the timeline. TCPA gates (9am–8pm local window, frequency caps, opt-outs) are enforced in code for BOTH voice and SMS. GET .../outreach/preview returns the exact per-channel renders for the selected stage — surfaced in the web console and Operator X deal views before arming.

Human checkpoints — ALWAYS gate, in code

Whatever the mode, these four require an explicit confirmed operator action. The autopilot can raise them (checkpoint records in the notification store); it has no code path that clears them:

  1. Starting a negotiationPOST .../negotiation/start {confirm:true}. dfy_autopilot.tick_deal only creates the negotiation_target checkpoint when a location shows interest.
  2. Approving negotiated terms — required whenever agreed terms sit at or near an envelope edge (within 1% of the share ceiling, or the term at a bound) or agent confidence is low (≥8 rounds, or the counterparty pushed out-of-mandate asks). Enforced on the transmit path itself: send_lease raises 409 until POST .../negotiation/approve-terms {confirm:true}.
  3. The lease signature momentsend_lease is the ONLY transmit path and demands a confirmed request. On convergence the autopilot raises the lease_send checkpoint ("lease ready to send") and stops.
  4. Money — the machine purchase is always a confirmed checkout (POST .../order/checkout {confirm:true} / POST /preorder); supplier orders above the monthly cap stop at an inventory_cap checkpoint whose approval also requires confirm:true.

Checkpoint inbox: GET /api/v1/dfy/checkpoints. Taking the linked action resolves the checkpoint automatically (start ⇢ negotiation_target, approve-terms ⇢ terms_approval, send ⇢ lease_send, payment ⇢ money, order approval ⇢ inventory_cap).

Negotiation

The operator sets a mandate (envelope): revenue-share target/max, term min/target/max, acceptable power arrangements, install window, deal-breakers. PUT .../negotiation/envelope.

The engine (app/dfy_negotiation.py) is deterministic — the LLM never decides money:

Every turn is logged (actor, message, proposal, reasoning, in-envelope flag) — the audit timeline the graph derives from.

The negotiation graph

GET .../deals/{id}/negotiation/graph — nodes: parties (operator, agent, location contact), every term-sheet version (offers/counters), escalations, the outcome, the contract; edges: mandates, proposed_by, counters, concedes_on (with the dimension that moved), led_to, signed.

The graph is derived deterministically from the turn log and materialized into the dfy_graphs store on every negotiation mutation (FLEET tier, reset-proof), so peers serve the same picture.

GET /api/v1/dfy/negotiation/graphs — the portfolio view: per-vertical convergence bands ("share converged 14–16% in transit hubs vs 18–20% in nightlife"), per-negotiation rollup.

Learning (grounded)

When a new negotiation starts, dfy_graph.prior_insights aggregates the operator's converged outcomes for matching venue keywords. If prior deals in the vertical converged above target, the agent opens just below the historical convergence (never above the envelope max) — and the turn's reasoning cites the exact prior deal ids that informed it.

Portfolio autopilot

PUT /api/v1/dfy/portfolio/settings — the switch plus guardrails:

{"autopilot": true, "autoApproveScore": 55, "maxAutoApprove": 3,
 "installMode": "team", "autoInventory": true,
 "inventoryMonthlyCapUsd": 250, "moneyThresholdUsd": 100}

Per-deal override: POST .../deals/{id}/autopilot {"enabled": true|false|null}.

With autopilot effective, tick_deal (run lazily on every deal read, plus POST /api/v1/dfy/autopilot/tick) advances what autonomy allows:

Install team + automated onboarding

POST .../install/dispatch {confirm:true} (or autopilot) creates a dfy_install work order on the Operator OS os_work_orders plumbing with an installer queue: open → assigned → scheduled → en_route → on_site → completed (forward-only; POST /api/v1/dfy/install/orders/{id}). The installer completes the same setup wizard (link serial → Nayax bind → checks → complete). Self-install stays a per-deal choice.

complete_install then onboards automatically: planogram seeded to max on the standard planogram (pricing from category defaults), payment rails verified, report stored on the deal. A failed Nayax bind raises the nayax_fail checkpoint instead of blocking the machine going live.

Autonomous inventory

dfy_autopilot.inventory_tick (portfolio tick / POST /api/v1/dfy/inventory/tick) rides the same planogram data the Fleet Foreman's restock lists use: aisles at/below 25% of max become a supplier order (SKUs, quantities, estimated wholesale cost = 55% of retail, badged estimated) plus a scheduled refill work order (dfy_refill).

Budget guardrail: per-machine monthly cap. Over-cap orders stop at pending_approval + an inventory_cap checkpoint; approval spends money so it requires confirm:true. POST .../inventory/orders/{id}/receive marks the refill done: restocks via the existing rails, closes the work order, and settles estimatedactual cost in the P&L.

The portfolio view

GET /api/v1/dfy/portfolio — "all you see is revenue": machines owned, deals in flight, machine capital value, month-to-date revenue (unified revenue ledger app/revenue.py), cost lines (inventory with estimated/actual basis, machine subscriptions), net MTD, per-machine net yield, and the checkpoint inbox — the only actions the operator must take.

Assistant tools: get_portfolio, get_dfy_pipeline, get_negotiation_graph (read-side; every checkpoint action stays behind the confirmed REST endpoints).

Operating modes — manual / auto / hybrid (app/dfy_modes.py)

Every machine (and in-flight deal) carries six independently-toggleable automation capabilities: placement, outreach, negotiation, contracting, install, inventory. All-on rolls up as Auto, all-off as Manual, mixed as Hybrid. Resolution order, enforced in code at every autonomous call-site (tick_deal, inventory_tick, process_counter, maybe_auto_contract):

  1. Concierge active → always on (the team + agents run it).
  2. Deal/machine toggle (PUT /api/v1/dfy/mode/{kind}/{id}).
  3. Legacy per-deal autopilot override (deal.autopilot).
  4. Portfolio capability defaults (settings.capabilities).
  5. The autopilot master switch.

Two gate semantics, deliberately different:

Mode switches have defined side effects (returned as transitions): queued (pending-approval) supplier orders are cancelled with their checkpoints when inventory flips off; placed orders stand. Capability conflicts resolve sanely: auto-negotiation + manual contracting negotiates to convergence then STOPS (no lease_send checkpoint; the operator sends it themselves).

Agents assist the manual paths, suggest-only and logged: the negotiation advisor (POST /deals/{id}/negotiation/advise) says what the engine would do and why without moving the standing offer; the operator authors real turns via POST /deals/{id}/negotiation/operator-turn (their terms are validated but not envelope-clamped — it's their mandate; out-of-envelope moves are flagged in the log). Pulse Mailer drafts intro emails (POST /deals/{id}/assist/outreach-draft); Fleet Foreman suggests restocks (GET /inventory/suggestions?machineNo=…) without creating orders. Every assist lands on the deal timeline.

Concierge tier — "hand it to us"

POST /api/v1/dfy/mode/{kind}/{id}/concierge {action: enroll|takeback, confirm: true} — a paid, confirm-gated, reversible handoff. Enrollment flips the machine to Auto with the Kiosk-X ops/install team on its work-order queues; the $99/mo fee prorates by enrolled days and lands in the machine's cost lines (so concierge machines show honest net profit). Takeback reverts to inherited toggles; the accrued fee stays owed.

Hardened semantics: one fee per placement — a deal and its linked machine can't both be enrolled (409). Enroll/takeback run the same defined mid-lifecycle transitions as a mode switch (a hand-run negotiation gets the mode_resume checkpoint instead of deadlocking agent-silent; a reverted-manual inventory cancels queued orders). Every mode/concierge entry point — read, write, and concierge — is tenant-isolated by machine/deal ownership.

Profit — machines as securities

GET /api/v1/dfy/portfolio/securities (also embedded in /portfolio) shows every machine like a security: revenue, sales, PROFIT (mode-blind — the math is identical for manual, auto, hybrid, concierge). Revenue nets out sales refunds (a $200-sold / $150-refunded machine earned $50). Cost lines per machine: inventory/supplier orders (estimated vs. actual basis, refunded orders cost nothing), machine subscription, protection plan, install-team cost, concierge fee. profitMtdUsd uses everything; profitActualMtdUsd excludes estimated lines; hasEstimates badges the split. Fleet rollups (fleet.revenueUsd/costsUsd/profitUsd, netYieldPct on machine capital) and modeCounts power the portfolio home on web + Operator X; losses render as negative numbers, never hidden.

Invest what you like — the managed fleet pool (app/dfy_invest.py)

Operators don't have to buy whole machines. POST /api/v1/dfy/fleets accepts any amount (investUsd, $50–$250k, confirm-gated like every money action); the capital lands in the operator's own managed pool — never a fabricated fraction of somebody else's machine, never commingled across tenants. Every time the pool balance covers the target model's price, the platform spawns a fully-managed (Hands-off) DFY deal funded from the pool and deducts the price; whatever is left stays visible as capital awaiting allocation. Payment is a real Stripe Checkout session (TEST mode) for the arbitrary amount — the sandbox falls back to the labelled simulated checkout — and the apply is idempotent on the Stripe event id (webhook, the /investments/{invId}/poll reconciliation, and the simulator all converge).

The platform's take is a 2% maintenance fee on pool-funded machines only:

fee = 2% × revenue  +  2% × max(0, revenue − costs − the revenue component)

computed month-to-date from the same honest figures the securities view uses. It shows up as its own cost line (costs.maintenanceFeeUsd, with an itemized maintenanceFee breakdown and a poolFunded flag) in /portfolio/securities — no hidden take; machines the operator bought outright never pay it. GET /api/v1/dfy/fleets returns the pool (balance, total invested, machines funded, fee pct) plus the investment history, and the pool rolls up into /portfolio for the web + Operator X home surfaces.

Persistence

All DFY stores are FLEET-tier and reset-proof (app/persistence.py): dfy_deals, dfy_notifications, dfy_graphs, dfy_portfolio, dfy_supplier_orders, dfy_modes, dfy_investments. A /sandbox/reset never forgets a paid pre-order, a sent contract, an unresolved checkpoint, an ordered restock, or a mode/concierge election; peer replicas apply each other's writes at runtime.

Webhooks + integrity

Environment

Variable Purpose
KIOSKX_DOCUMENSO_DFY_TEMPLATE_ID Placement-agreement template
KIOSKX_STRIPE_WEBHOOK_SECRET_DFY Dedicated order webhook secret (falls back to the shared one)
KIOSKX_DFY_OUTREACH_RETRY_HOURS Touch cadence (default 24)
KIOSKX_DFY_CALL_WINDOW_START/END TCPA-style call window (default 9–20 local), applies to voice AND SMS
KIOSKX_DFY_VOICE_APP_REF Dedicated "Kiosk-X DFY Outreach" Fonoster autopilot app
KIOSKX_SMS_MCP_URL Telnyx MCP gateway (default https://telnyx-mcp.intelli-verse-x.ai/)
KIOSKX_SMS_MCP_TOKEN Telnyx MCP bearer (the Telnyx API key); unset = SMS channel honestly unavailable
KIOSKX_SMS_FROM SMS sender (defaults to the voice toll-free +18337512480)