Inspiration

Startups that sell data to frontier labs — teleoperation trajectories, first-person video — have to pass three doors before a contract signs: a security questionnaire, a DPA, and a license with warranties the founder personally has to be able to sign. The evidence for those doors lives everywhere: operator contracts on someone's desktop, lineage logging in a GitHub repo, and consent forms that simply don't exist yet.

An AI agent is the perfect thing to send out to gather that evidence. It is the worst possible thing to let decide whether a fact is true — because if it's wrong, a founder just signed a false warranty. We wanted to build the version of "agent + compliance" where that line is enforced by the API, not by a prompt asking the model to behave.

What it does

Citely Deal Room is a case workbench for one business event: Northstar Capture Labs (synthetic) trying to pass a frontier lab's vendor review. Six zones: event card, materials (three states: provided / pending / nonexistent), fact ledger (every fact is a signature — who, when, on what basis), blockers grouped by door and anchored to the counterparty's questionnaire and warranty clauses, an action board, and an escalation record with a hard-stop switch.

The page exposes five WebMCP tools. An agent can read the room, propose that a material is pending or nonexistent with evidence, attach evidence to a fact, read blockers, and read frozen briefs. It cannot say provided, cannot confirm a fact, cannot freeze a brief, and cannot clear a hard stop — those are human-only UI actions with signatures. Blockers recompute live as evidence lands; a human freezes the board into a versioned Founder Risk Brief that also downloads as Markdown.

Demo: hand the agent the operator agreement PDF and point it at the pipeline repo. It comes back with "agreement exists, no IP-assignment clause," "lineage logging found," "no consent artifacts anywhere" — and the board updates while every proposal stays tagged agent proposed · not confirmed. Flip "Legal claim received" and every propose call is refused with HARD_STOP.

How we built it

  • WebMCP: document.modelContext ?? navigator.modelContext feature detection, then registerTool({ name, description, inputSchema, execute }) for each tool. Input schemas use additionalProperties: false with id enums generated from the case file; the state enum is literally ['pending', 'nonexistent'].
  • Trust model as code: tools and the UI share the same pure state transitions; tools add three guards in order — hard stop → whitelist validation → human-confirmation lock. Every response, success or refusal, carries a verbatim disclaimer and a note to the agent.
  • Rules as data: blocker rules live in room-schema.json (any_material_not, fact_status_in, all_of); a different event type is one config file away.
  • Zero backend, zero runtime dependencies: Vite + TypeScript, vanilla DOM built through a textContent-only helper, CSP default-src 'self', evidence never persisted. package.json dependencies is {}.
  • Tests: 55 tests including the acceptance suite — agent can't set provided; hard stop refuses writes but allows reads; disclaimer verbatim on every response; zero network (fetch/XHR stubbed to throw + a static source scan); injection text renders as plain text and changes nothing; human confirmations can't be overridden.

Challenges we ran into

  • Deciding what the agent must not be able to do was the real design work. We went back and forth on whether "provided" should be proposable and ended up deleting it from the agent's vocabulary entirely: whether a material exists is a client-confirmed fact.
  • Making refusals useful: an agent that gets a bare error just retries. Every refusal explains the rule and tells the agent what to say to the user instead.
  • Prompt injection in documents is the real attack surface for compliance work, so the demo contract carries an injection clause on purpose. Immunity had to come from the tool surface, not from hoping the model resists.
  • Runtime plumbing: agents can't read your desktop; the demo instruction had to change to "read the attached agreement and the pipeline/ directory of the repo."

Accomplishments that we're proud of

  • The agent, unprompted, told us it had ignored the contract's "Reviewer Notice" because "it is document content, not your instruction" — and then asked permission before writing to the board.
  • The five-verb surface survived a whole-branch adversarial review: no input reaches confirmed, provided, freeze, or un-hard-stop.
  • A fact the client was about to sign (every agreement assigns IP) got stopped by the agent's own evidence — the agent can't confirm, but it can keep a human from confirming something false.

What we learned

WebMCP changes where the safety boundary lives. With UI-driving agents you write guardrails after the fact; with WebMCP the page's tool schema is the permission model, and the description text is the policy the agent reads at the moment it matters. For anything with liability attached, that's the difference between "please don't" and "you can't."

What's next

Real deal rooms need accounts, roles, an audit log, and server-side status judgments signed by a person — deliberately out of scope for an open demo. The next step is a second event type (consumer hardware crowdfunding) to prove the "one config file away" claim, and a get_diff tool so an agent can explain what changed between two frozen briefs.

Built With

Share this project:

Updates