Turning product data into user understanding, without the hallucinations.

Personal Project·2026–now·Web App·view project ↗

Problem

I've seen this pattern across a lot of product teams. They do the research, but the rest of the team ends up ignoring the results. Not because they don't want to use it, but because they can't even connect what they found with the decisions in front of them. The persona ends up in a Figma file or a PPT. The interview transcripts live in a Notion doc that not even the person who did the interview goes back to revisit.

I realized the gap wasn't effort, it was how that data reached the team. So I built Resense: to help product teams get value from what they already had, without needing a specialist to interpret it or spending hours on a persona artifact that ends up in the bottom drawer.

The AI stack

The generation pipeline maps raw product data through JTBD and anti-optimism guardrails, structuring it into Synthetic Users via OpenRouter with Claude Sonnet in production and Gemini Flash in development. I found the two-model strategy through testing: dev iteration needed speed, production needed reasoning quality for complex synthesis. They serve different jobs.

RAG-powered evaluation retrieves relevant source chunks to ground pattern analysis in actual user data. This is what lets non-specialist product teams trust the output: every insight traces back to a source.

Prompt caching does two things: it reduces token cost and cuts response latency by 63%. On a research tool where people are waiting for a synthesis before a meeting, that speed matters as much as the content itself.

Every request goes through a BYOK layer for the free tier: users bring their own API key (OpenRouter, Anthropic, OpenAI, or Google), which never leaves the browser. Auto-detection routes keys to the right provider. The server never stores or logs guest keys.

The design decisions

Calibrating trust was the hardest design problem. LLMs feel authoritative even when they're guessing, so the biggest risk in the product was delivering outputs that teams used without questioning. I landed on three systems that address this directly.

Source citations per attribute: every insight in a Synthetic User profile links back to the source it came from. Inferred fields are tagged [Inferred] in the output. Users see exactly what came from their data and what was model inference.

The anti-optimism guardrail is an explicit prohibition list in the system prompt: four patterns that make a generation invalid. The layer forces the model to describe friction points as specific situations, when, where, what happens. If a friction point is too vague, the generation fails validation.

The triangulation score measures source diversity (behavioral, attitudinal, contextual) with a multiplier: one source type yields 0.6x confidence, three types yield 1.0x. The ConfidenceBadge component renders this as a warning, informational, or confirmation state, and shows exactly which source types are missing.

Evaluation and improvements are off the critical path, triggered on-demand. The model call for profiles closes, then users can trigger a separate call for patterns and recommendations when they need it. Two seconds on demand keeps the main loop fast, which is where the team spends most of their time.

The failure mode

When the model doesn't have enough data to generate a reliable Synthetic User, it says so. I built a DataHint panel that surfaces three signals: an analysis of what's missing, the specific data types needed, and a suggested research question to go get them. The panel uses progressive disclosure. The analysis is visible by default. The gaps and suggested question expand on interaction.

I decided that showing a low-quality Synthetic User with no signals of uncertainty was more dangerous than being honest about insufficient data. Teams that trust bad research make worse decisions, and avoiding that was exactly the point.

Before any user data reaches the LLM, an anonymizer strips PII: emails, Brazilian CPF numbers, phone numbers, IP addresses, and names in context. The model receives only anonymized text. Data at rest uses field-level encryption. I have no access to raw user content. That costs me admin convenience. It was worth it.

How I work with AI

Resense didn't start as a product. It started as my own process. Synthetic Users are how I get a first read on who I'm designing for in hours instead of weeks, then I validate the ones that matter with real people. A starting point, not a replacement for research.

The same instinct runs through the rest of my work. I prototype navigable flows in Claude Code and Cursor, so engineering gets something they can click through instead of a static screen, which is where handoffs usually break. And I built a design agent that carries my design principles and a set of Synthetic Users as context, running concept and usability checks against them before I sit down with a real user. When I compared its verdicts to real sessions, they landed close enough to trust as a first pass.

AI isn't a step I bolt onto the end. It's in the research, the prototype, and the test, so I spend my time on the decisions instead of the setup.

What's next

I want Synthetic Users that evolve on their own as new product data comes in, without manual regeneration. And a research agent that learns from correction signals and suggests what to validate next based on the gaps it already flagged. The goal is a system that gets more useful the more the team uses it.

next read

Coisas.space →

Coisas.space is a product that uses AI to categorize every saved link and let you chat with what you've saved, to actually learn from it. Matching is semantic: ask about React and it surfaces links that never mention React in the title. I used AI across the whole process, from research to ideation to testing.