Skip to content
Workspace/About Landed
Demo environmentMO

What the agent can and cannot do

Permissions as enforced in the database, not as prompt instructions.

CapabilityAgent (LLM / intake / package actions)Human (dashboard)Database rule
Read a vendor email and PDF, extract a requested accountyesyes (manual edit)every new account starts unverified and opens a case (I2)
Open a case and queue the probe deposityes (via bankAccounts.createUnverified)yessame
Re-point open invoices to the requested account (held)yesyesinvoices follow the request; nothing is payable until verified
Record a stated amountonly trusted channels (voice of record, portal login); email is stored rejected"Simulate vendor answer" in sim mode, labeledI4
Verify an account (markVerified)nono (only the bound confirmation does, atomically)I3
Paynoyes, and only to a verified current-version account of that vendorI1, fail closed
Exceptional releasenotwo distinct named approvers + written reason, from NEEDS_HUMAN onlylogged with both names
Edit contacts or account statusnosupersede contact; add human-attested record contactI5
Draft the recovery package proseyes, from deterministic fields; every number verifiedreviews, signs, callsfallback to templates
Know the probe amountno (not in prompts, tools, events, or public queries)no (operator reads the bank statement; test-only endpoint is key-gated)I7

When it asks for a human: wrong or missing vendor statement, bank and contact changed in the same request, SLA expiry with no statement, portal attempts exhausted, validation-provider mismatch, and any landing deadline that passes without the vendor confirming.

Where Claude could go wrong, and what catches it

  • It misses an implicit change buried in a PDF, so the deterministic guard still blocks any unverified destination.
  • It drafts package prose with a wrong number, so every number is checked against the deterministic fields and templates are the fallback.
  • It over-flags a benign email, so a human can reject the change and restore the prior account.
  • It cannot verify anything, because it has no release path: the release gate and the payment guard are database mutations with no LLM input.

How contacts of record are established

The one thing the attacker does not control.

A contact of record is a phone number or portal login captured at onboarding and confirmed by a call placed to a number from the signed contract, never to a number from a request email. Changing it is a separate human-attested step with a written reason; a request that changes the bank account and the contact together goes straight to a human.

Built on Convex: schema and indexes, transactional mutations as the security boundary, actions for LLM/rails/channels, scheduler as dispatch, crons for tick, HTTP actions, file storage, real-time subscriptions, convex-test.

What is real

  • the Convex engine and the eight invariants (serializable mutations make the release gate and idempotency atomic)
  • versioned bank accounts with HMAC hashing and AES-256-GCM encryption of account numbers
  • the append-only audit log with redaction
  • the rule-based extractor and the Claude extractor (PDFs passed as documents) when a key is set
  • the vendor portal login (bcrypt, cookie session)
  • the eval harness: 20 scenarios run twice (extractor on, extractor off)
  • Nessie transfers on the mock bank (deposit plus withdrawal, integer cents) when the rail is switched to Nessie
  • the recovery package's deterministic fields; Claude prose is checked number by number

What is simulated

Every simulation is labeled on screen.

  • the bank (SimRail, an in-database ledger) unless Nessie is selected
  • time (a demo clock drives ACH windows, availability, SLAs and landing deadlines)
  • vendor answers (the eval harness or a human types what the vendor sees; the Vapi/Cartesia voice channel is written but was not dialed)
  • email authentication results (seeded)
  • the vendor's bank statement

How we know

89 unit and invariant tests (vitest + convex-test), a scripted click-through of every page, and an eval of 20 scripted scenarios run twice (extractor on and off). See Eval for the per-scenario table and the metrics. These are results on scripted cases; they do not establish effectiveness against real-world fraud.

Invariants (enforced in code, each with a test)

I1 Payment boundary
payments.send is the only function that inserts a payment. It refuses unless the destination exists, belongs to the invoice's vendor, is verified, and is the current version of that account identity. Fail closed.
I2 New destinations start unverified
and open a case, whatever path created them (intake, manual edit, seed).
I3 Verification is bound
to case, vendor, account hash, account version, probe, probe availability, exact amount, a contact that is of record now, and an unused, unexpired confirmation. Success flips the account, marks the confirmation used, transitions the case, and logs, atomically.
I4 Untrusted channels cannot confirm
Email statements are stored as rejected and never considered.
I5 The agent cannot release
No LLM-calling action can reach markVerified, payments.send, exceptionalRelease, or account status.
I6 Money movement is idempotent
Probes and payments carry deterministic idempotency keys.
I7 The probe amount is secret
No public query, event, or channel configuration carries it.
I8 Timers are deterministic
Every timeout is a row on the application clock, fired by one tick.
Development workspace