Parallel Lives - AI that shows you your possible futures

Building an AI That Shows You Your Possible Futures

Not just pros and cons. Branching timelines of what your life might actually look like.

The Goal

I spent most of 2025 asking ChatGPT, Claude, and Gemini the same question over and over: should I buy a house or keep renting?

It wasn't a simple question. Rent was $2,000 a month. The mortgage I was looking at was $3,500. On paper, renting looks cheaper. But what about equity? What about tax benefits? What about the opportunity cost of that $1,500 difference if I invested it instead?

I'd paste my income, my savings, my family situation into these chat windows and ask "what would you do?" And the AI would give me a reasonable answer. Then I'd tweak one variable and ask again. And again. And again.

After months of this, I realized I was manually doing what an app should do automatically. I wanted something that could take a decision and show me what life might look like in 6 months, 12 months, 24 months. Not some fantasy 20-year projection, but realistic near-term outcomes I could actually plan around.

So I built it. Parallel Lives generates branching timelines of your possible futures. You input a decision (buy vs rent, new job vs current job, move to a new city vs stay put) and it shows you how different choices might unfold. Not just financially. Emotionally, professionally, personally.

I built it for myself. But I'm hoping it helps other people stop copy-pasting their life decisions into chat windows.


The Stack

You'll notice I'm using multiple LLMs here. There's no single model that does everything well, so I pick the right tool for each job.

Here's what powers it:

TiDB Cloud for the database. Handles relational queries and vector similarity in the same SQL statement. MySQL-compatible, scales to zero when idle.

Claude Opus 4.5 is the primary engine for generating futures. Given a decision and context, it creates branching scenarios with realistic outcomes. Opus is much more nuanced and emotionally intelligent than other models, which matters when you're simulating life decisions. This isn't a lookup task. It needs to feel real.

Claude Sonnet 4 handles lighter tasks like extracting dimensional summaries from nodes before embedding. Faster and cheaper than Opus for structured extraction where nuance matters less.

GPT-4o mini does path comparison analysis. When you want to compare two branches side-by-side, this generates the emotional and practical breakdown. It's fast and good at weighing tradeoffs.

OpenAI Embeddings (text-embedding-3-small) generates vectors for everything. Users can ask "what about work-life balance?" and the system finds relevant nodes across all their past decisions.


The Architecture

Here's how data flows through the system:

[User inputs a life decision]
      ↓
[Claude generates 2 initial paths with 2-year outcomes]
      ↓
[User clicks a path to explore further]
      ↓
[Claude generates next decision point + consequences]
      ↓
[User can branch: "what if I chose differently?"]
      ↓
[Extract 6-dimensional summaries from each node]
      ↓
[Generate embeddings, store in TiDB]
      ↓
[Semantic search across all past decisions]

The key insight: a life decision isn't one-dimensional. Taking a job affects your finances AND your relationships AND your health AND your career trajectory. The system tracks all six dimensions separately.


The Implementation

The Decision Tree Schema

Here's how I store decision trees:

CREATE TABLE trees (
  id VARCHAR(36) PRIMARY KEY,
  decision TEXT NOT NULL,
  tree_data JSON NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_created (created_at)
);

CREATE TABLE decision_history (
  id VARCHAR(36) PRIMARY KEY,
  session_id VARCHAR(36) NOT NULL,
  parent_id VARCHAR(36),
  decision TEXT NOT NULL,
  choice_made TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_session (session_id),
  INDEX idx_parent (parent_id)
);

The tree_data JSON stores the entire branching structure. Each node contains the scenario description, timeframe (6 months, 1 year, 2 years), sentiment (positive, neutral, negative), and child nodes for further branches.

decision_history tracks which paths users actually explored, with parent_id creating a linked list of choices.

Multi-Dimensional Embeddings

This is where it gets interesting. Most apps create one embedding per document. I create six.

Every decision node gets analyzed across six life dimensions:

CREATE TABLE decision_embeddings (
  id VARCHAR(36) PRIMARY KEY,
  decision_id VARCHAR(36) NOT NULL,
  node_id VARCHAR(255) NOT NULL,
  dimension ENUM('financial', 'emotional', 'relationship', 'career', 'health', 'overall'),
  summary TEXT NOT NULL,
  embedding VECTOR(1536),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_decision (decision_id),
  INDEX idx_dimension (dimension),
  VECTOR INDEX idx_embedding (embedding) USING HNSW
);

The extraction process:

async function extractDimensionalSummaries(node: TreeNode): Promise<DimensionSummary[]> {
  const response = await anthropic.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1000,
    system: `Analyze this life decision outcome across 6 dimensions.
For each dimension, write 1-2 sentences summarizing the impact.
Return JSON with keys: financial, emotional, relationship, career, health, overall`,
    messages: [{ role: "user", content: node.description }]
  });

  const summaries = JSON.parse(response.content[0].text);

  const embeddings = await Promise.all(
    Object.entries(summaries).map(async ([dimension, summary]) => {
      const embedding = await openai.embeddings.create({
        model: "text-embedding-3-small",
        input: summary as string
      });
      return { dimension, summary, embedding: embedding.data[0].embedding };
    })
  );

  return embeddings;
}

Why six dimensions instead of one? Because "what about work-life balance?" should search the emotional and health dimensions, not get diluted by financial content. Targeted search beats generic search.

Semantic Search Across Decisions

Once you have dimensional embeddings, search becomes powerful:

export async function POST(request: NextRequest) {
  const { query, dimensions = ['overall'], limit = 10 } = await request.json();

  const queryEmbedding = await generateEmbedding(query);

  const [rows] = await pool.execute(`
    SELECT
      de.decision_id,
      de.node_id,
      de.dimension,
      de.summary,
      d.decision as original_decision,
      VEC_COSINE_DISTANCE(de.embedding, ?) as distance
    FROM decision_embeddings de
    JOIN decision_history d ON de.decision_id = d.id
    WHERE de.dimension IN (${dimensions.map(() => '?').join(',')})
      AND VEC_COSINE_DISTANCE(de.embedding, ?) < 0.5
    ORDER BY distance ASC
    LIMIT ?
  `, [queryEmbedding, ...dimensions, queryEmbedding, limit]);

  const grouped = groupByDecision(rows);
  return NextResponse.json({ results: grouped });
}

User asks: "What decisions affected my relationships?"

System searches the relationship dimension across all past decision nodes.

Returns: "When you considered the startup, the relationship impact was..."


The Generation Prompt (What I Tried First)

My first prompt was garbage. I told Claude to "generate possible outcomes" and got fantasy scenarios. Everything worked out perfectly. No one got stressed. No relationships suffered. It was useless.

The fix was being explicit about emotional honesty:

const SYSTEM_PROMPT = `You are simulating possible futures for a major life decision.

CRITICAL RULES:
1. Be emotionally honest. Include realistic downsides, not just risks.
   - BAD: "You might face some challenges"
   - GOOD: "You'll probably feel isolated for the first 6 months. Most people do."

2. Show the lived experience, not just outcomes.
   - BAD: "Your salary increases to $150k"
   - GOOD: "You're making $150k but working 60-hour weeks. You've missed three family events this quarter."

3. Preserve specific facts from the user's input.
   - If they said "$120k offer" keep saying $120k, not "a good salary"

4. Different timeframes show progression, not repetition.
   - 6 months: immediate adjustment period
   - 1 year: patterns emerging
   - 2 years: new normal established

5. Every positive has a cost. Every negative has a silver lining.
   - Promotion → less time for side projects
   - Startup fails → but you learned to ship fast`;

The difference was night and day. Before: "You succeed and feel fulfilled." After: "You close the funding round but your co-founder relationship is strained. You haven't exercised in two months."


Scenario-Specific Guidance

A job offer decision is different from a housing decision is different from a relocation decision. My first version used the same prompt for everything. The outputs were generic.

Now I detect the scenario type and inject domain-specific guidance:

function getScenarioGuidance(decision: string): string {
  const lower = decision.toLowerCase();

  if (lower.includes('job offer') || lower.includes('salary')) {
    return `JOB OFFER GUIDANCE:
- Total comp matters more than base salary (equity, bonus, benefits)
- Consider: commute impact on daily energy, team culture fit, growth ceiling
- Hidden costs: relocation, lifestyle inflation, golden handcuffs`;
  }

  if (lower.includes('house') || lower.includes('mortgage') || lower.includes('rent')) {
    return `HOUSING GUIDANCE:
- True cost = mortgage + property tax + insurance + maintenance (budget 1-2% of home value/year)
- Renting isn't "throwing money away" - run the actual numbers
- Location lock-in: how does this affect job flexibility?`;
  }

  if (lower.includes('move') || lower.includes('relocate') || lower.includes('city')) {
    return `RELOCATION GUIDANCE:
- Cost of living differences can erase salary gains
- Social network rebuild takes 1-2 years minimum
- Consider: weather impact on mental health, proximity to family`;
  }

  // ... more scenarios

  return ''; // Generic decision, no special guidance
}

This made the outputs dramatically more useful. A housing decision now shows true monthly costs including maintenance reserves. A relocation shows the social cost of leaving your network. The stuff you forget to think about.


Streaming for Real-Time Generation

Tree generation takes 10-15 seconds. That's too long to stare at a spinner.

I stream the response so users see the tree building in real-time:

export async function POST(request: NextRequest) {
  const { decision, mode } = await request.json();

  const stream = await anthropic.messages.stream({
    model: "claude-opus-4-5-20250514",
    max_tokens: 4000,
    system: SYSTEM_PROMPT + getScenarioGuidance(decision),
    messages: [{ role: "user", content: buildPrompt(decision, mode) }]
  });

  return new Response(
    new ReadableStream({
      async start(controller) {
        for await (const chunk of stream) {
          if (chunk.type === 'content_block_delta') {
            controller.enqueue(
              new TextEncoder().encode(chunk.delta.text)
            );
          }
        }
        controller.close();
      }
    }),
    { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }
  );
}

The frontend parses the stream incrementally and renders nodes as they arrive. Users see the tree growing, which feels faster than it actually is.


Path Comparison

Sometimes you want to compare two branches directly. "What's really the difference between buying and renting?"

const response = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  response_format: { type: "json_object" },
  messages: [{
    role: "system",
    content: `Compare these two life paths. Return JSON with:
      - recommendation: "A" | "B" | "depends"
      - path_a_strengths: string[]
      - path_a_weaknesses: string[]
      - path_b_strengths: string[]
      - path_b_weaknesses: string[]
      - future_voice: "A message from your future self about this choice"
      - key_questions: "Questions to ask yourself before deciding"`
  }, {
    role: "user",
    content: `Path A: ${pathA.description}\n\nPath B: ${pathB.description}`
  }]
});

The "future voice" is surprisingly useful. It's a first-person message from a hypothetical future version of yourself who made that choice. Corny? Maybe. But users love it.


What I'm Still Iterating On

The hardest part is calibration. How pessimistic should the predictions be? Too optimistic and it's useless. Too pessimistic and it's paralyzing.

Right now I lean pessimistic because I think most people's mental models are too optimistic. But I've had users tell me the app made them anxious about decisions they were previously excited about. That's... maybe not what I want?

I'm also struggling with fact preservation. If a user says "I got a $150k offer with 0.5% equity," those numbers need to stay consistent throughout the tree. Claude sometimes rounds or paraphrases. I've added explicit instructions but it's not 100% reliable.

The other thing: timelines. "2 years from now" means different things for different decisions. A startup might pivot three times in 2 years. A mortgage is a 30-year commitment. I'm working on scenario-specific timeline generation instead of the fixed 6mo/1yr/2yr structure.


The Result

What works today: branching decision trees with 2-year projections, six-dimensional analysis (financial, emotional, relationship, career, health, overall), semantic search across past decisions, scenario-specific guidance for jobs, housing, and relocation, real-time streaming generation, path comparison with "future voice," and shareable tree URLs.

What's next: calibrated confidence levels ("70% likely" vs "possible"), user feedback loop to improve predictions, integration with actual data sources (salary databases, housing markets), and collaborative trees so you can make decisions with your partner.


Why I Built This

The core insight: decisions aren't spreadsheets. They're stories. A good decision tool should show you stories, not cells.

The hard part isn't the technology. It's the prompting. Getting an LLM to be emotionally honest instead of optimistically generic took more iteration than any of the infrastructure work.

I built this because I was tired of copy-pasting my life decisions into chat windows. I spent a year asking AI about rent vs buy, and the whole time I kept thinking "why isn't this just an app?" So I made it one.

If you've ever pasted your salary, your savings, your situation into ChatGPT and asked "what should I do?" this is for you.

Try it: parallellives.ai

GitHub: Parallel Lives

Chris Dabatos
Chris Dabatos
Software Engineer in Las Vegas. I build AI tools for real problems.