How we built it
One function prices the plan.
Liquidity is a Next.js app around a pure TypeScript engine. The demos call that engine. A model may explain the result or propose another look. It does not approve, and it does not invent a balance.
How a decision is made
The browser never prices a plan. Jeronimo, Ishayu's Capital Studio, and Your situation call optimizeFunding and render what comes back. They do not store a plan. Claude and CREAO are separate routes. They can explain or propose. They cannot approve, and they cannot invent a cent.
Surfaces
Web app and extension
Jeronimo's /demo is a group dinner for about ten at BJ's Restaurant & Brewhouse. Ishayu's Capital Studio is /demo?studio=ishayu: $40,000 of international tuition, borrowing against an asset. Your situation is a sandbox a judge can edit. The extension pairs with a one-time code, then asks what a purchase would do.
Server
Next.js routes
The browser calls same-origin handlers. It does not import the engine.
Decision
optimizeFunding
Pure TypeScript. Integer cents. It does not read the clock, so the same inputs return the same plan.
Record
Convex
Sessions, stored plans, the audit, and pairing hashes. The dinner story does not write them. Without a deployment URL, the same functions run in memory.
Called by routes, not by the engine
Claude
Explains the stored plan. Cannot choose it.
CREAO
Proposes a recovery. The engine recomputes it.
Alpaca
Daily bars for volatility, and only when the gate is open.
Same inputs, same plan
Inputs
Accounts, tax lots, obligations, inflows, and policies, plus which bills to fund. Money is integer cents. Dates are YYYY-MM-DD, with no local-timezone shift.
Candidates
At most 25. Checking then savings, protected cash, savings then checking, specific-lot sales ordered by estimated tax, one approved existing line, and a few hybrids. That is the set we rank, not every possible plan.
Constraints
The protected lane skips inflows that are not verified. Hypothetical credit and property equity stay out. A sale that does not match a verified holding is refused before approval. The engine never calls a bank. Browser execute is rejected. Approval records a decision, and no money moves.
Ranking
Full coverage, then no policy violations, then lowest modeled cash cost in cents, then lower risk, then fewer actions, then a stable candidate id. Modeled cost includes illustrative federal tax on a simulated sale.
Where the demos call it
The dollar amounts on the demos are return values from this function. They are not typed into the screen.
POST /api/jeronimo/analyze
Jeronimo's group dinner at BJ's Restaurant & Brewhouse. Read-only, on the server fixture.
POST /api/capital-studio
Ishayu, at /demo?studio=ishayu. $40,000 of international tuition, borrowing against an asset. Returns the picture. It does not store a plan.
POST /api/custom/analyze
Your situation, at /demo?studio=custom. A judge edits the books. The engine prices that picture and does not store a plan.
POST /api/purchase-impact
The extension, after a pairing code. Read-only.
POST /api/plan
Still runs the engine and storePlan for a session. /demo does not call it.
What we used
Five products show up in the code or in the design file the screens were built from. Each card is a call site.
POST /api/assistant
Claude
- Why
- Ask has to explain a plan the engine already chose. The model is not allowed to pick a different one.
- Fit
- The call is a single Messages request with no tools. A dollar figure that is not in the engine's fact list is thrown away, and the route quotes those facts instead.
- Where
- lib/agents/explainer posts to api.anthropic.com/v1/messages from POST /api/assistant, which explains a stored plan. The dinner story does not call it. With no API key, the answer is a fixture quotation, not a model run.
- Tradeoff
- OpenAI and Gemini can return constrained JSON too. We gave up tool use, so this call cannot approve, execute, or write a balance.
POST /api/recovery
CREAO
- Why
- When a plan breaks, something has to propose a recovery without blocking the person who approves.
- Fit
- The hosted run is asynchronous. Its proposal is not stored as a new balance. The engine recomputes, and a candidate whose sale does not match a verified holding is left out.
- Where
- lib/providers/creao calls POST /runs and GET /runs/{id} on agent.creao.ai from POST and GET /api/recovery. The dinner story does not start a run. A missing key or agent id stays a fixture, not a finished run.
- Tradeoff
- A tool-calling loop on Claude or OpenAI could live in this process. We gave that up. A developer.creao.ai project key is rejected; the live path uses the app trigger.
Session store
Convex
- Why
- An anonymous session keeps its accounts, stored plan, audit chain, and extension pairing code on their own rows.
- Fit
- The Next handler runs the engine, then calls a mutation. Convex does not import the optimizer. getWorkspaceSnapshot is the session read. The dinner story does not subscribe to it.
- Where
- convex/schema.ts holds the rows. convex/extension.ts stores a SHA-256 of the pairing code, not the code. app/api/_lib/gateway.ts uses HTTP when a deployment URL is set, and the same functions in memory otherwise.
- Tradeoff
- Supabase would give Postgres, and Firebase a similar document model. We gave up SQL joins. No deployment URL means an in-memory gateway, not Convex cloud.
POST /api/liquidation
Alpaca
- Why
- A modeled sale can use historical daily bars for volatility. The sale price stays the scenario mark.
- Fit
- The client is read-only market data. It never calls a trading or brokerage-account endpoint. Live bars require the market-data flag and both keys. Otherwise the route keeps fixture history.
- Where
- POST /api/liquidation calls lib/providers/alpaca, which reads data.alpaca.markets only when the gate is open. The dinner story does not request bars.
- Tradeoff
- Polygon also sells daily bars, and adds quotes and trades. We gave those up. The default build sends no Alpaca request.
Design file
Figma
- Why
- The landing page was drawn as frames before the components that implement it.
- Fit
- Figma is the shared picture of those screens. It is not a runtime API, and this page has no frame. The app does not fetch the file.
- Where
- The file is xGGE4pCIWR49jnvTwEe6tr. Landing frames are mapped in components/landing/README.md. This page has no frame.
- Tradeoff
- Sketch, or laying the pages out only in code, would skip a shared frame file. We gave up a design tool the running app can query.