# Project export: theo

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: a cognition copilot that distills your thoughts from pen to perspective
- Devpost: https://devpost.com/software/theo-k1enau
- GitHub: https://github.com/RALS-TreeHacks-25/backend
- Demo: https://github.com/RALS-TreeHacks-25/frontend
- Video: https://www.youtube.com/embed/qbKc0O2uQv8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Elastic: Best use of Elasticsearch Serverless ($2,000 Cash [1st] & $1,000 Cash [2nd]))
- Team: 3 GitHub contributor(s) — Andy Xu (12 commits), rohan335 (9 commits), LarryPi314 (6 commits)

## Devpost submission (written by the team)

### Overview

Overview theo is a cognition copilot that distills your thoughts from pen to perspective. You can think of theo in two ways. In buddhist thinking, papañcha is the word for conceptual proliferation — when you’re overthinking about the world and have a swirl of thoughts attacking you from all directions until you’re stressed but don’t know what to do. theo helps you take this swirl of thoughts and distill them into understanding and action. From the perspective of an “epidemic of busyness”, we are constantly surrounded by stimuli and noise that prevent us from taking 5 minutes to take a step back. theo is a cognition OS that analyzes your written notes, prompts you for reflection, sparks connections with your earlier thoughts, and suggests actionable steps in your calendar. A lot of people are building a second brain for productivity — we are building a second brain for cognition. Problem Everyone is in a constant swirl of work, studies, and chores, with little time in a day to step back and process emotions and feelings, reflect on actions and priorities, and identify values and goals. On top of that, with social media, informational noise around us, and pressing global challenges, making this conscious step to recap your day became only more difficult. In the midst of these modern factors, people end up feeling overwhelmed, lost, or anxious, without the means to truly understand why or how to work through it. Helping people understand themselves better is a key to a more lucid, productive, purposeful future for humanity. Solution theo is an AI-powered cognitive workflow that helps users clarify their thinking through writing. When a user inputs journeys – whether scattered ideas, reflections, or unstructured thoughts – theo extracts valuable insights, identifies patterns across past entries, and asks intelligent follow-up questions to spark self-reflection. For every thought journey you write down, theo can leave three types of annotations: connections: relations between the current journey and your previous web of thoughts; questions: focused questions based on the recent notes to spark reflection; actions: suggestions for calendar events to take actionable steps to work on your goals -- one click integration with calendar apps. Additionally, it can prompt you with personalized questions to start your journeys. Why theo Unlike a standard note-taking app, theo transforms raw cognition into structured insight. Unlike a typical agent, it aims to gently guide rather than overcorrect and oversuggest. For example, in annotations, theo first and foremost shows the user their previous thoughts, but deliberately does not give its own analysis on top of them. Additionally, the number of question annotations is kept to be low to not cause a feeling of overwhelm. We assist the user in distilling their thinking instead of thinking for them. Accomplishments that we're proud of We’re proud of building an AI tool that doesn’t just optimize for efficiency but also for depth of thinking. Seeing theo make successful connections between separate thoughts and building valuable insights was a major win. We also prioritized user experience, making sure theo feels like a natural extension of the user’s mind rather than an overwhelming AI assistant.

### How we built it

For this hackathon, we tried incorporating a myriad of new and exciting frameworks into our tech stack. We relied on a React-Native frontend as well as Firebase’s Cloud Functions for our backend, as we wanted our users to be able to journal on the fly. We used Firestore as a general-purpose no-SQL datastore and ElasticSearch to run vector search on our embeddings. Finally, we used Mistral’s fast models for our agentic behavior. This combination of frameworks enabled us to achieve flexible functionality when it came to meeting our app’s functional requirements.

### Challenges we ran into

The architecture of the foundation – the full stack app itself was smooth sailing. However, as many developers who work with AI agents know, we quickly learned that AI agents are unreliable, with LLM output outright hallucinating inconsistent behavior. In particular, we struggled with querying elastic search for semantically similar journals using a vector search, as Elastic search’s semantic search function returned to us different output every time we called one of our HTTP endpoints. Getting the annotations working proved an enormous difficulty as well, requiring us to design an entire mental module associated with indexing the characters on screen.

### What we learned

We gained deeper insight into how AI can facilitate cognition rather than just automate tasks. Through testing, we saw firsthand how prompting users with the right reflection questions can unlock new insights, giving us a glimpse of how AGI might go about functioning. On the technical side, we learned a lot about embeddings, vector similarity, and how to design an effective note-retrieval system.

### What's next

We see theo evolving into a more personalized and adaptive cognition copilot. Future iterations will include improved contextual understanding, allowing theo to better detect a user’s current mindset and tailor its prompts accordingly, and orienting user within their complex web of thoughts by visualizing connections between different journeys. theo is more than a productivity workflow — it’s a thinking partner for a clearer mind.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 12 recognized source files, 35 KB.
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Firebase (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (21 of 21)

```
.firebaserc
.gitignore
firebase.json
firestore.indexes.json
firestore.rules
functions/.eslintrc.js
functions/.expo/README.md
functions/.expo/settings.json
functions/.gitignore
functions/firebase.js
functions/index.js
functions/journals.js
functions/llmPreprocess.js
functions/mistral.js
functions/package.json
functions/prompts.js
functions/rag.js
functions/users.js
package.json
public/404.html
public/index.html
```

### Dependencies

- functions/package.json: @elastic/elasticsearch@^8.17.0, @mistralai/mistralai@^1.5.0, dotenv@^16.4.7, elasticsearch@^16.7.3, eslint@^8.15.0, eslint-config-google@^0.14.0, express@^4.21.2, firebase-admin@^11.8.0, firebase-functions@^4.3.1, firebase-functions-test@^3.1.0, firebase-tools@^13.31.1
- package.json: @mistralai/mistralai@^1.5.0

### Recent commits (newest first)

- Merge pull request #7 from RALS-TreeHacks-25/promptRobust
- working prototype
- Merge pull request #6 from RALS-TreeHacks-25/promptRobust
- updated prompts
- Merge pull request #5 from RALS-TreeHacks-25/promptRobust
- fix prompting and journals
- dealt w stff
- Merge pull request #4 from RALS-TreeHacks-25/Larry
- Merge branch 'main' into Larry
- dsafdsaf
- added more apis
- quickie
- Merge pull request #3 from RALS-TreeHacks-25/andyContext
- added user context to prompts
- smart user awareness
- Merge pull request #2 from RALS-TreeHacks-25/llm-context
- new llm preprocessing & events
- Merge pull request #1 from RALS-TreeHacks-25/prompting
- new prompts for llm
- added multi-user functionality for databases

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "type": "module",
  "dependencies": {
    "@mistralai/mistralai": "^1.5.0"
  }
}

```

### functions/package.json

```
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "18"
  },
  "main": "index.js",
  "type": "module",
  "dependencies": {
    "@elastic/elasticsearch": "^8.17.0",
    "@mistralai/mistralai": "^1.5.0",
    "dotenv": "^16.4.7",
    "elasticsearch": "^16.7.3",
    "express": "^4.21.2",
    "firebase-admin": "^11.8.0",
    "firebase-functions": "^4.3.1",
    "firebase-tools": "^13.31.1"
  },
  "devDependencies": {
    "eslint": "^8.15.0",
    "eslint-config-google": "^0.14.0",
    "firebase-functions-test": "^3.1.0"
  },
  "private": true
}

```

### functions/index.js

```javascript
import { Client } from "@elastic/elasticsearch";
import { https, firestore } from 'firebase-functions';
import { users } from './users.js';
import { journals } from './journals.js';
import dotenv from 'dotenv';

// Configure dotenv
dotenv.config({ path: '../secrets/.env' });

// Export HTTP functions
export const usersApi = https.onRequest(users);
export const journalsApi = https.onRequest(journals);
```

### functions/firebase.js

```javascript
import admin from 'firebase-admin';
import { readFileSync } from 'fs';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const serviceAccount = JSON.parse(
    readFileSync(path.join(__dirname, '../secrets/serviceAccount.json'))
);

if (admin.apps.length === 0) {
    admin.initializeApp({
        credential: admin.credential.cert(serviceAccount)
    });
}

export default admin;
```

### functions/.eslintrc.js

```javascript
module.exports = {
  env: {
    es6: true,
    node: true,
  },
  parserOptions: {
    "ecmaVersion": 2018,
  },
  extends: [
    "eslint:recommended",
    "google",
  ],
  rules: {
    "no-restricted-globals": ["error", "name", "length"],
    "prefer-arrow-callback": "error",
    "quotes": ["error", "double", {"allowTemplateLiterals": true}],
  },
  overrides: [
    {
      files: ["**/*.spec.*"],
      env: {
        mocha: true,
      },
      rules: {},
    },
  ],
  globals: {},
};

```

### functions/mistral.js

```javascript
import { Mistral } from '@mistralai/mistralai';
import dotenv from 'dotenv';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.join(__dirname, '../secrets/.env') });

// Initialize Mistral client
const mistral = new Mistral(process.env.MISTRAL_API_KEY);

// Add the askMistral function
export async function askMistral(prompt) {
    try {
        const response = await mistral.chat.complete({
            model: "mistral-large-latest",
            messages: [{ role: "user", content: prompt }],
        });
        return response.choices[0].message.content;
    } catch (error) {
        console.error('Error calling Mistral API:', error);
        throw error;
    }
}
```

### public/404.html

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Page Not Found</title>

    <style media="screen">
      body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
      #message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px 16px; border-radius: 3px; }
      #message h3 { color: #888; font-weight: normal; font-size: 16px; margin: 16px 0 12px; }
      #message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
      #message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
      #message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
      #message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
      #message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
      #load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
      @media (max-width: 600px) {
        body, #message { margin-top: 0; background: white; box-shadow: none; }
        body { border-top: 16px solid #ffa100; }
      }
    </style>
  </head>
  <body>
    <div id="message">
      <h2>404</h2>
      <h1>Page Not Found</h1>
      <p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>
      <h3>Why am I seeing this?</h3>
      <p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured <code>public</code> directory.</p>
    </div>
  </body>
</html>

```

### functions/llmPreprocess.js

```javascript
import admin from './firebase.js';

import { generateKeywordPhrasesPrompt, generateQuestionPrompt, generateEventPrompt, generateBrainstormPrompt } from './prompts.js';
import { Client } from '@elastic/elasticsearch';
import { createDoc, searchKeyPhrase } from './rag.js';
import { askMistral } from './mistral.js';

const db = admin.firestore();

async function getJournals(user) {
    try {
        let journals = await db.collection('journals').where('user', '==', user).get();
        let journalsArray = [];
        journals.forEach(journal => {
            journalsArray.push(journal.data());
        });
        return journalsArray;
    }
    catch (error) {
        console.log("error with getting journals");
        console.log(error);
    }
}

// creates a string with all previous journal entries
export async function preprocessJournalsLLM(userId) {
    const journals = await getJournals(userId);
    // between each journal should be the title of the entry as well
    const journalString = journals.map(journal => `${journal.title}\n${journal.text}`).join("\n\n");
    return journalString;
}

export async function prepareUserInfo(userId){
    try {
        const userDoc = await db.collection('users').doc(userId).get();
        const user = userDoc.data();
        const userInfo = `User Info. name: ${user.name}, date of birth: ${user.dob}, pronouns: ${user.pronouns}, city: ${user.city}, interests: ${user.interests}, bio: ${user.bio}`;
        return userInfo;
    }
    catch (error) {
        console.log("error with getting user info");
        console.log(error);
    }
}

export async function getUserBrainstormPrompt(userId){
    const userInfo = await prepareUserInfo(userId);
    const journalString = await preprocessJournalsLLM(userId);
    let prompt = generateBrainstormPrompt + "\n\n" + userInfo + "\n\n" + "User journal entries:\n" + journalString;
    // parse out ```json and ``` from the string
    prompt = prompt.replace("```json", "").replace("```", "");
    return prompt;
}
```

### functions/users.js

```javascript
import express from 'express';
import cors from 'cors';
import admin from './firebase.js';
import { user } from 'firebase-functions/v1/auth';
import { getUserBrainstormPrompt } from './llmPreprocess.js';
import { askMistral } from './mistral.js';

const users = express()
users.use(cors({origin: true}))

const db = admin.firestore()

users.post('/createUser', async (req, res) => {
    try{
        await db.collection('users').doc(req.body.uid).set(req.body)
        res.status(200).json({message: "user created successfully!"})
    } catch(error){
        res.status(500).json(error)
    }
})

// Read all the users
users.get('/getUsers', (req, res) => {
  db.collection('users').get()
    .then(snapshot => {
      const items = [];
      snapshot.forEach(doc => items.push({ id: doc.id, ...doc.data() }));
      res.status(200).json(items);
    })
    .catch(error => res.status(500).json({ error: error.message }));
});

// Read a single user
users.post('/getUser/', (req, res) => {
  const itemId = req.body.uid;
  db.collection('users').doc(itemId).get()
    .then(doc => {
      if (!doc.exists) {
        res.status(404).json({ error: 'Item not found' });
      } else {
        res.status(200).json({ id: doc.id, ...doc.data() });
      }
    })
    .catch(error => res.status(500).json({ error: error.message }));
});

// Update a single user
users.post('/updateUser', (req, res) => {
  const itemId = req.body.user.uid;
  const updatedItem = req.body.user;
  console.log("Reached users REST API!")
  console.log(updatedItem)
  console.log(itemId)
  db.collection('users').doc(itemId).update(updatedItem)
    .then(() => res.status(200).json({ message: 'Item updated successfully' }))
    .catch(error => res.status(500).json({ error: error.message }));
});

// Delete a single user
users.delete('/deleteUser', (req, res) => {
  const itemId = req.body.uid;
  db.collection('users').doc(itemId).delete()
    .then(() => res.status(200).json({ message: 'Item deleted successfully' }))
    .catch(error => res.status(500).json({ error: error.message }));
});

users.get("/getUsersCarousel/", async (req, res) => {
  try{
    let prompt = await getUserBrainstormPrompt(req.query.user);
    console.log("prompt: ", prompt);
    let response = await askMistral(prompt);
    console.log("response 123: ", response);
    // parse out ```json and ``` from the string
    response = response.replace("```json", "").replace("```", "");
    
    let prompts = JSON.parse(response);
    console.log("prompts: ", prompts);
    res.status(200).json(prompts);
  } catch(error){
    res.status(500).json(error);
    console.log("error: ", error);
  }
});

export { users }
```

### functions/rag.js

```javascript
import { Mistral } from "@mistralai/mistralai";
import { Client } from '@elastic/elasticsearch';
import dotenv from 'dotenv';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.join(__dirname, '../secrets/.env') });
const mistral = new Mistral({
    apiKey: process.env.MISTRAL_API_KEY ?? "",
});

const elasticClient = new Client({
    node: process.env.ELASTIC_ENDPOINT ?? "",
    auth: {
        apiKey: process.env.ELASTIC_API_ENTRIES ?? "",
    }
});

async function getEmbeddings(text){      
    try{
        const result = await mistral.embeddings.create({
            inputs: [text],
            model: "mistral-embed",
        });
        
        const mistralEmbedding = result.data[0].embedding;
        // Handle the result
        return mistralEmbedding;
    } catch (error) {
        console.log("error with getting embedding");
        console.log(error);
    }
    
}

export async function createDoc(firebaseId, text, userId){
    const embeddingResponse = await getEmbeddings(text);
    console.log("gotten embedding: ", embeddingResponse);
    const documentWithEmbedding = {
        text: text,
        embedding: embeddingResponse,
        id: firebaseId,
        userId: userId,
    };

    console.log("document with embedding:");
    console.log(documentWithEmbedding);

    try {
        const res = await elasticClient.index({
            index: "j2",
            document: documentWithEmbedding,
        });
    }
    catch (error) {
        console.log("error with creating new vector in elastic");
        console.log(error);
    }
    console.log("entry created successfully!");
}


export async function searchKeyPhrase(keyphrase, threshold, userId) {
    const keyphraseVector = await getEmbeddings(keyphrase);
    try {
        const searchResponse = await elasticClient.search({
            index: "j2",
            body: {
                query: {
                    script_score: {
                        query: {
                            // bool: {
                            //     must: [
                            //         { term: { userId: userId } },
                            //         { exists: { field: "embedding" } }
                            //     ]
                            // }
                            exists: { field: "embedding" }
                        },
                        script: {
                            source: "cosineSimilarity(params.query_vector, 'embedding')",
                            params: { query_vector: keyphraseVector }
                        }
                    }
                },
                size: 100  // Limit to top 5 most similar results
            }
        });

        const hits = searchResponse.hits.hits;
        console.log("Number of hits:", hits.length);
        if (hits.length == 0) {
            return;
        }

        const journals = hits.map(hit => ({
            ...hit._source,
            score: hit._score
        }));
        
        // find if sim score is > threshold
        if (journals && journals[0].score > threshold){
            console.log("Found journal with score above threshold");
            return journals[0].id;
        }
        else{
            console.log("sim score below threshold", journals[0].score);
            return;
        }
    } catch(error) {
        console.error('Search error:', error);
    }
}
```

[3 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]