Bilateral Marketplace v2
Onoots Proptech runs a two-sided marketplace with two legs:
- Side A — Developer. Inventory management and liquidation software: detection of stale units, predictive absorption, and the argument that “a discount costs less than idle inventory.”
- Side B — Buyer. An automated fiduciary buying desk, monetized with a success fee on the negotiated savings, with anti-bypass (a buyer representation agreement signed before the unit is revealed) and swarm buying (aggregating demand into block offers to developers).
Both legs are operated by the brokerage (a hybrid A+B model), plus a limited B2C intake that feeds the agents.
High-level architecture
Everything asynchronous (absorption, due diligence, post-signature follow-up, block-offer preparation) travels over the swarm engine — see The swarm engine.
The five phases
| Phase | Name | Delivers |
|---|---|---|
| F1 | Developer Desk + bilateral bridge | Inventory absorption, matching briefs against developer lots, an immutable price baseline |
| F2 | Anti-bypass + Due Diligence | Sign-before-reveal via OnootsSign, candidate masking, a Due-Diligence Auditor agent, discount tokens, handoff to a deal |
| F3 | Success fee | A savings/fee ledger kept separate from the commission cascade, counsel sign-off |
| F4 | Limited B2C intake | A public bilingual form → agent triage → converts into a brief |
| F5 | Swarm Buying | Buyer cohorts (all with signed agreements), aggregated block offers, presentation behind a dual-agency checkpoint |
Detailed flows
Bilateral bridge + matching (F1)
Buyer outreach scans two sources: individual listings and developer lots
(inventory in available state, from the brief’s brokerage). Each candidate — listing or
lot — captures an immutable list-price snapshot when it’s created. That snapshot is the
baseline for the F3 fee: every day without it, the future fee of today’s candidates becomes
impossible to backfill with integrity.
Anti-bypass + Due Diligence (F2)
- Masking: before the agreement, even a read that passes row-level security receives the masked shape — the listing/lot id and address never leave the server without a signed agreement.
- Honest “encrypted agreement”: a private (encrypted-at-rest) bucket + SHA-256 + an optional on-chain anchor = access-controlled and tamper-evident.
- Discount tokens: issued after signature, redeemable exactly once against a deal from the same brief.
Success fee (F3)
fee = fee_rate × max(0, baseline − negotiated_price), where the baseline is the F1
list-price snapshot and the negotiated price is the sale price at close. Currencies must
match or the engine refuses (it never fabricates FX). The fee ledger is separate from
the commission cascade (existing commission logic is untouched).
If the regulatory pack requires confirmation and the brokerage has not recorded a legal sign-off, the fee event is recorded as waived with a reason — it never accrues silently.
Swarm buying (F5)
- Anti-bypass invariant at the schema level: a cohort member row must reference a signed agreement (enforced by a non-null foreign key) — no buyer without a signed agreement can enter a cohort, guaranteed by the database.
- Tenant consistency: a composite key prevents a member or offer from diverging from its cohort’s brokerage.
- Presentation is the hard gate: presenting a block offer maps to a licensed-broker-only
action and requires a recorded dual-agency sign-off. Without it, presentation returns
dual_agency_signoff_required(fail-closed). - Developers see only aggregated demand: a block offer stores the count, band and total — never member identities.
The swarm engine
All orchestration is an outbox pattern: the app inserts rows into an events table; a
relay process reads them and starts the matching durable workflow; the worker runs
the workflow, which calls back into the app over an authenticated internal endpoint (a
shared secret, constant-time compared). The app never blocks on the workflow engine: if the
worker is down, events stay pending and drain when it returns.
| Event | Workflow | Effect | Phase |
|---|---|---|---|
inventory.synced | absorption | inventory snapshots | F1 |
buyer_brief.submitted | buyer outreach | matching → buyer candidates | F1 |
dd.requested | buyer due diligence | DD report (Auditor agent) | F2 |
agreement.signed | agreement follow-up | post-signature side-effects | F2 |
buyer_cohort.formed | block-offer prep | block-offer draft | F5 |
Data model (concepts)
The domain model is designed so guarantees hold at the database level, not just in application code:
- Buyer candidates carry the immutable list-price snapshot (the fee baseline).
- Buyer agreements are a small state machine (draft → sent → signed).
- Discount tokens are single-use, tied to one brief.
- Buyer-fee events are a ledger separate from commissions (brokers can read; sensitive writes are service-role only).
- Buyer cohorts, members and block offers power swarm buying, with the signed-agreement invariant baked into the schema.
Cross-cutting: row-level security scopes reads to the agent/broker; sensitive writes go through a privileged service role only.
Regulatory gates (fail-closed)
Everything ships blocked until counsel or a licensed professional signs off — the system runs in production but does not “go to market” until these are lifted:
- Buyer agreement (F2): before revealing a unit’s identity.
- Fee sign-off (F3): without a recorded legal sign-off, fees are recorded as waived.
- Dual-agency (F5): without a recorded sign-off, a block offer cannot be presented.
- Intake terms (F4): consent text is a counsel placeholder until finalized.
Deployment shape
The app, an async worker + relay, and the database run as separate services. A shared internal secret authenticates the callbacks from the worker back into the app. When a new workflow is added, the worker is deployed first so the app never routes to a workflow the worker doesn’t yet know.