DEV Community

Cover image for Ardum: An Agent-Guided Yoga Retreat Matcher That Reasons Out Loud, Remembers You, and Never Shows You a Loading Spinner
Papa
Papa

Posted on

Ardum: An Agent-Guided Yoga Retreat Matcher That Reasons Out Loud, Remembers You, and Never Shows You a Loading Spinner

Ardum is an agentic yoga retreat matching platform. The name is mudra reversed — a mudra is a seal that directs energy and intention; Ardum opens the seal and begins the search.

The premise: most "AI matching" products are a form with a spinner and a recommendation at the end. We wanted something closer to what a good teacher actually does — listens, reasons visibly, remembers you, and never makes the moment feel transactional. The whole experience is anchored by Mira, a persistent agent persona (a breathing orb, a warm second-person voice) who is present at every step: intake, matching, the match letter, booking, and post-booking prep.

A full run through the app looks like this:

Calibrate — three honest questions (energy, budget, social comfort) plus an optional five-second pose sample processed entirely in-browser via MediaPipe. Raw video never leaves the tab.
Match — Mira reasons over a pool of retreat attestations and streams her logic live, structured as Gherkin steps (Given/When/Then), so every signal she weighs is visible and disagreeable. While she reasons, you're not staring at a spinner — more on that below.
Book — one click, no MetaMask, no seed phrase, no gas.

The interesting engineering is mostly in how steps 2 and 3 avoid feeling like a form and a checkout.

The matching phase is not a loading screen

This is the part of Ardum I'd point other builders to first. Matching takes a few seconds of real reasoning time, and instead of hiding that behind a spinner, Ardum turns it into an interactive aesthetic experience:

A 12-image curated pool, each image scored on 15 aesthetic dimensions
An ambient drone synthesized live via the Web Audio API — no audio files, fully deterministic
The user's reactions (resonate / skip, weighted by how long they linger) build a live preference vector
That vector drives which images get shown next (bandit-style selection), shifts the drone in real time, and later gets woven directly into Mira's match letter: "While I was thinking, you were drawn to ocean and warm tones. That tells me something about where you'd thrive."

It's tiered for cost: the curated-image tier is free and instant, and only one paid image-generation call (via fal.ai, ~$0.003) happens per match — for a generated "retreat vision" image personalized to what the user resonated with. If no image API key is configured, it falls back to curated stock photography automatically. Total marginal cost per match: about $0.018.

Reasoning you can see, question, and re-run

Mira's reasoning isn't a black box you either accept or reject. Two things make it inspectable:

Two lenses on every match. Every match result runs the same scoring registry through two different weightings — a "Restorative" lens and a "Movement" lens — and shows both side by side. When they agree, the recommendation is robust. When they disagree, the user sees exactly where the two perspectives diverge, computed deterministically from the same rule set (no extra LLM calls).

Counterfactual re-scoring. After a match, the user can ask "what if I'd weighted this differently?" and Mira re-scores the same attestation pool under a different composite weight balance — energy-heavy, social-heavy, or budget-heavy presets — and shows the alternate top match with the same step-by-step reasoning underneath. Changing your mind doesn't mean starting over.

Both features come from one design decision: matching logic lives in a single AXES registry that both the deterministic scorer and the LLM prompt read from. Change a rule once, and the local scorer, the LLM's reasoning, and the "why" behind every match all update together — nothing to keep in sync by hand.

Booking without touching a wallet

Ardum has three distinct people interacting with money, and each gets a different abstraction because their needs don't overlap:

Practitioners booking a retreat get a Magic social login (Google) that creates an embedded wallet, upgraded via Particle's Universal Account SDK (EIP-7702) so a deposit in any token on any chain settles automatically on Arbitrum against a deployed escrow contract. No chain selection, no gas.
Operators attesting a retreat listing get Particle Auth plus a ZeroDev ERC-4337 smart account, with gas sponsored by a paymaster and session keys so batch attestation writes don't require re-signing each one.
Drop-in class payments run through Openfort with the x402 HTTP payment protocol — a GET returns a 402 with payment requirements, a signed authorization gets posted back, and it settles on Base Sepolia with sponsored gas.

Particle's EIP-7702 accounts and ZeroDev's ERC-4337 accounts can't coexist on the same address, which is exactly why practitioners and operators need separate rails rather than one universal wallet abstraction trying to do both jobs.

All of it — retreat listings, bookings, class access — gets written as attestations to 0G Storage, which stays the single source of truth regardless of which wallet flow touched it.

Giving Mira actual memory

The one piece missing from all of the above: Mira forgot everyone the moment they closed the tab. The original stopgap was a localStorage fingerprint — three fields, a 1–30 day window, no real recall, nothing that survived a new device.

We replaced that with Cognee, a hybrid graph-vector memory store, wired in behind four verbs:

typescriptawait cognee.remember(userId, "energy=low, budget=1k-2k, matched to Restorative Yin in Ubud, score 0.87");
const memory = await cognee.recall(userId, "What do I know about this practitioner?");
await cognee.improve(); // enrich the graph in the background
await cognee.forget(userId); // right-to-be-forgotten, one tap

remember() fires after intake, after each match, and after each booking. recall() runs before the match stream starts and before Mira writes her letter, returning a structured MemoryContext (energy trajectory, past matches, past bookings, notes) that feeds both Mira's opening line — "You've been to Restorative Yin in Ubud before — this builds on that" — and the matching prompt itself, so the agent reasons about trajectory, not just a single snapshot.

A /memory page shows practitioners exactly what Mira knows, including the actual knowledge graph rendered as a force-directed visualization — nodes are entities, edges are relationships like matched_with or booked_at. There's a one-tap "forget me" button. We were nervous this would feel invasive; in testing it was one of the most-visited pages in the app. People want to see what an AI remembers about them, not just be told to trust it.

Every Cognee call degrades silently — missing API key, exhausted credits, network failure — the app just runs without cross-session recall rather than breaking the match flow. That's a hard requirement for anything sitting on the critical path of a real product, not just a nice-to-have.

What we learned

  1. The "loading state" is real estate. Matching takes a few seconds no matter what. Spending that time on an aesthetic journey instead of a spinner turned dead time into a second, genuinely useful signal about the user's preferences — for free, before any LLM call happens.

  2. Visible reasoning builds trust faster than accurate reasoning. Users didn't just want a good match; they wanted to see why, disagree with a step, and re-run it under different assumptions. The counterfactual feature came directly out of watching people ask "but what if—" out loud during testing.

  3. Don't force one wallet abstraction to serve two different users. Practitioners and operators have genuinely different needs (cross-chain deposits vs. gasless batch writes), and trying to unify them under one account model would have made both worse.

  4. Memory changes the product category, not just the tech. Before real cross-session memory, Ardum was a matching engine you'd use once. The moment Mira recognizes a returning practitioner with specific, accurate detail, it stops being a tool and starts being something closer to a relationship.

  5. Transparency de-risks the creepy parts. Both the reasoning stream and the memory graph expose things that could easily feel invasive. Both became trust-builders instead, because the user could see and control them rather than just being told to trust a black box.

The stack

LayerTechnologyFrameworkNext.js 16 (App Router, React 19, Turbopack)Agent personaMira (custom voice + orb component)Attestations0G Storage SDKAgent reasoning0G Compute Router (OpenAI-compatible, streaming), deterministic local fallbackMemoryCognee (hybrid graph-vector)Pose calibrationMediaPipe Tasks Vision (in-browser)Aesthetic journeyCurated image pool + Web Audio API drone + fal.ai (Tier 2 generative imagery)Session persistenceSupabase (with in-memory fallback)Practitioner bookingMagic + Particle Universal Account (EIP-7702) + Arbitrum escrowOperator attestationsParticle Auth + ZeroDev (ERC-4337, gas sponsorship, session keys)Drop-in paymentsOpenfort + x402 (Base Sepolia)Real-time reasoningServer-Sent EventsDeploymentVercel

Try it

Live app: ardum.vercel.app
Source code: github.com/udirobert/ardum
Memory page: ardum.vercel.app/memory — the Cognee graph in action

Ardum runs fully in demo mode with no environment variables set — a deterministic local matcher, in-memory session store, and ten seeded retreats — so you can try the whole flow without spinning up any of the above.

Top comments (0)