# How to make an AI clone of your CEO for the world's biggest hackathon

> Evil Martians and Bolt.new teamed up to build an AI clone of their CEO Eric Simons using Tavus to power real-time video calls for the world’s largest hackathon.

- Date: 2025-06-23T00:00:00.000Z
- Authors: Nina Torgunakova, Ivan Eltsov, Travis Turner
- Categories: Developer Products, Design, Case Study, AI
- URL: https://evilmartians.com/chronicles/how-to-make-an-ai-clone-of-your-ceo-worlds-biggest-hackathon-tavus-bolt-new

---

We’re increasingly seeing people connect with AI the same way they’d talk to a real person: face-to-face, voice-to-voice, with natural expressions and gestures. AI talks, listens, looks alive, helps users, and even inspires them. In this post, we’ll share how we helped our client Bolt.new ship an AI clone of its creator, Eric Simons, to the landing page of the world’s largest hackathon. Ready to learn how to build and integrate a more human AI experience on your site?

For context, Evil Martians met the StackBlitz team in 2021, and we’ve been a core tech partner helping scale their platform ever since. Then, in 2024, they launched Bolt.new–an AI-powered agent that allows you to prompt, run, edit, and deploy full-stack applications directly from your browser.

Read more on how we supported the transformation into Bolt.new and scaled their platform to 3M+ developers.
*Read also Evil Martians' article*: https://evilmartians.com/chronicles/bolt-new-from-stackblitz-how-they-surfed-the-ai-wave-with-no-wipeouts

The market response to Bolt.new was extraordinary: in 4 weeks, [they went from zero to $4M in annual recurring revenue](https://www.anthropic.com/customers/stackblitz), which has grown exponentially since then. 

Now, pushing the boundaries even more, **Bolt.new is aiming to set a Guinness World Record by kicking off the [world’s largest hackathon](https://hackathon.dev/)**. Starting on May 30th, this hackathon offers over $1 million in prizes, and welcomes builders and entrepreneurs of all backgrounds and skill levels. It’s not just a milestone for Bolt.new, it’s a landmark moment for the whole tech community, with thousands of participants joining in more than 75 countries.

*Image: The main page of the hackathon*

---

*We’ve helped StackBlitz scale bolt.new from 0 to $20M ARR+ in 2 months* [Contact Evil Martians](https://evilmartians.com/contact-us)

---

An extraordinary event like this can’t go on without some extraordinary features. What would be the highlight here? **How about having the CEO available 24/7 to all 100K+ participants, personally discussing their hackathon ideas, and inspiring them along the way?**

Of course, no human is physically capable of such a thing. We would have to clone the CEO. Well, what sounded like a joke ten years ago, is now completely possible with the help of [Tavus.io](http://Tavus.io), the OS for human-AI interaction.

Evil Martians helped Bolt.new implement an AI ​​clone of Bolt.new CEO Eric with Tavus.io on the hackathon site. [You can try it right now](https://hackathon.dev/)! And then sit back, we’ll tell you how we did it.

<blockquote class="twitter-tweet" data-dnt="true" align="center"><p lang="en" dir="ltr">I&#39;ve been trained into an AI model.<br><br>My voice, what I look like, 3d model of my face, even my personality.<br><br>The folks <a href="https://twitter.com/heytavus?ref_src=twsrc%5Etfw">@heytavus</a> even turned it into something you can actually talk to. It&#39;s freaking wild. You gotta try it<br><br>🤯👇 <a href="https://t.co/lUrh2s0wSF">pic.twitter.com/lUrh2s0wSF</a></p>&mdash; Eric Simons (@EricSimons) <a href="https://twitter.com/EricSimons/status/1928893262330552791?ref_src=twsrc%5Etfw">May 31, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## Enter Tavus

So, what is Tavus? Tavus is a leading AI video research company that allows developer teams to build AI Agents that see, hear, respond, and look human, all in real time.

*Image: The Tavus.io landing page*

Each Tavus AI agent consists of a _persona_ and a _replica_. 

A _persona_ is the "character" or "personality" and contains a configuration for that agent. For example, you can create a persona for "Anna the sales agent" or "Paulo the interviewer", or ‘Eric, creator of Bolt". Each persona is a separate instance which runs on the Tavus servers, and you need to create a “connection” to be able to interact with it.

*Meet Evil Martians' client*: https://evilmartians.com/clients/stackblitz

Learn more about creating your own persona using simple system prompts [here](https://docs.tavus.io/sections/conversational-video-interface/creating-a-persona); or, [once you've obtained your API key](https://docs.tavus.io/sections/guides/api-key-guide), you can use one of the ready personas from the [stock library](https://docs.tavus.io/sections/conversational-video-interface/stock-personas).

A _replica_ is the "appearance" of the agent: a realistic video model of a human created using the Phoenix Model. Replicas learn how someone speaks, how they look and sound, and have natural face (including lip, cheek, nose, chin) movements and expressions.

Just as you can do with personas, you can [create your own replica via the API](https://docs.tavus.io/sections/guides/replica-training-guide) (basically, it works like you’re cloning yourself and Tavus.io even [has a guide on this](https://docs.tavus.io/sections/replicas/replica-training)), or you can use a stock replica: browse available replicas from [Tavus Developer Portal](https://platform.tavus.io/) and take its ID for future API calls.

Essentially, you're calling your AI agent the same way you would call a human. The Tavus persona receives your video and audio input, then generates video and audio output which you can render. All of the interactions happen in real time, so it really feels like just a regular call with your friend.

## The code that makes this work

Let’s move on to show how you can integrate your own AI agent to your site. You'll need three credentials to start: a Tavus API key, the ID of your replica, and the ID of your persona.

Once you have the necessary credentials, the first thing to do is [create a conversation](https://docs.tavus.io/api-reference/conversations/create-conversation) using the Tavus API, then [join the call](https://docs.daily.co/reference/daily-js/instance-methods/join) using the Daily API. Daily is a library that allows us to build or embed video and audio chat applications, and you can find more information on it in [its documentation](https://docs.daily.co/).

```ts
type IConversation = {
  conversation_url: string; // direct link to join the conversation
  replica_id: string | null;
  persona_id: string | null;
  custom_greeting: string | null; // optional custom greeting
  // … other properties as per Tavus documentation
};
```

Now, we need to install the `@daily-co/daily-react` library which allows us to use Daily in our React application. In the main component, we’ll import and use the `DailyProvider`. The `DailyProvider` gives the component access to the Daily call object. It stores call state and initiates a call object instance.

We also don’t want to start a call any time a user opens the page (since we don’t want to scare anyone). Instead, we’ll show a popup with a call to action:

*Image: The call CTA block*

If the user clicks on “Start AI Video Call”, we start the conversation by invoking the corresponding endpoint from Tavus API. If the user clicks “Not now”, we dismiss the popup (in this case, we transform it to a small sticky button, so users can always start the call if they change their mind).

```ts
import { DailyProvider } from "@daily-co/daily-react";
import { createConversation} from "./api";

const Main = () => {
 const [conversation, setConversation] = useState();

 const handleStartConversation = async () => {
   const conversation = await createConversation();
   setConversation(conversation);
   // we also handle the loading and error states here
 };

 const StartCallButton = () => (

     Start AI Video Call

 );

 const CTABlock = () => (

     Need ideas? Brainstorm with Bolt CEO Eric Simons

       Not now

 );

return (

     {conversation ? (

     ) : }

 );
}
```

`createConversation` invokes a corresponding Netlify function. This step was implemented in order to avoid exposing our Tavus API key directly on the frontend side.

```ts
export const createConversation = async (): Promise => {
 try {
   const response = await fetch("/.netlify/functions/create-conversation", {
     method: "POST",
   });
   … // handling errors and return the response
};
```

In the Netlify function, we invoke Tavus API like so:

```ts
fetch("https://tavusapi.com/v2/conversations", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": TAVUS_API_KEY,
  },
  body: JSON.stringify({
    persona_id: ..., // replace with a valid persona ID
    replica_id: ..., // replace with a valid replica ID
    custom_greeting: GREETINGS[Math.floor(Math.random() * GREETINGS.length)],
    // picks a random personalized hackathon welcome message from the GREETINGS array
  }),
});
```

Now we’ll create the `Call` component that we mentioned in the main component above:

```ts
import {
 DailyAudio,
 useParticipantIds,
 useDaily,
} from "@daily-co/daily-react";

export const Call = ({ conversation }: Props) => {
 const daily = useDaily(); // returns current call object instance
 const remoteParticipantIds = useParticipantIds({ filter: "remote" }); // we need this to receive the participant id of our agent

 useEffect(() => {
   if (conversation && daily) {
     const { conversation_url } = conversation;
     daily.join({
       url: conversation_url, // join a conversation by URL
     });
   }
 }, [daily, conversation]);

 const AIAgentView = () => {
   return remoteParticipantIds.length > 0 ? (

   ) : (
      // if no participants, our AI agent wasn’t connected yet
   );
 };

  return (
     <>

        // when enabled and call is configured, DailyAudio will automatically subscribe to the active speaker's audio track
     </>
   );
}
```

We also need to implement the feature for ending the conversation using its ID:

```ts
 const handleEndConversation = async () => {
   await endConversation(conversation.conversation_id);
   // handle the loading and error states here
 };

const ExitCallButton = () => (

 );
```

The `endConversation` function invokes the corresponding Netlify function:

```ts
const endConversation = async (conversationId: string) => {
 try {
   const response = await fetch(`/.netlify/functions/end-conversation`, {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
     },
     body: JSON.stringify({ conversationId }),
   });

   … // handling errors and return the response
};
```

In the Netlify function, we invoke the finish conversation method this way:

```ts
fetch(
   `https://tavusapi.com/v2/conversations/${conversationId}/end`,
   {
     method: "POST",
     headers: { "x-api-key": TAVUS_API_KEY },
   }
);
```

We also wanted to add a "self" view users can see themselves. In the `Call` component, we import the `useLocalSessionId` hook, and use it like this:

```ts
import { useLocalSessionId } from "@daily-co/daily-react";

const localSessionId = useLocalSessionId();

const SelfieView = () => {
   return remoteParticipantIds.length > 0 && localSessionId ? (

   ) : null; // don’t display anything until AI agent joins the call
 };
```

We want to show the selfie view near the AI agent view in the `Call` component:

*Image: The selfie view*

```ts
return (
   <>

      // add selfie view

   </>
 );
 ```

Now AI Eric can see us, and we can see AI Eric. That’s something already!

Let’s now implement a simple (but awesome) feature to enhance the user experience: we’ll add a button to enable or disable the microphone, and also an indicator if a user is currently speaking or listening. In our `Call` component:

```ts
import {
 ...,
 useLocalSessionId,
 useAudioTrack,
 useActiveSpeakerId,
} from "@daily-co/daily-react";

const localSessionId = useLocalSessionId(); // need to track a user’s activity on their local device
const activeSpeakerId = useActiveSpeakerId(); // subscribe to the active speaker change event
const localAudio = useAudioTrack(localSessionId); // subscribe to information about the user’s audio track
const isMicEnabled = !localAudio.isOff;
```

The indicator:

```ts

  {localSessionId === activeSpeakerId ? 
    Speaking 
    : 
    Listening
  }

```

For the mic toggle:

```ts
const toggleAudio = useCallback(() => {
  daily.setLocalAudio(!isMicEnabled); // updates the local mic state
}, [isMicEnabled]);

  {isMicEnabled ? (

  ) : (

  )}

```

We now see an indicator if you’re listening or speaking, plus a button to switch the mic on/off:

*Image: The call view plus mic controls*

## The first challenge

Now, it’s time to talk about our first challenge. As there can be thousands of users chatting with AI Eric simultaneously, in order to avoid concurrency problems, we had to implement some conversational limits. Tavus recommends a conversation limit of 2-5 minutes and a basic IP-based limit of 1-4 sessions per day.

So, we implemented both. Below, we’ll provide you with an example of how we implemented a conversational limit. We also decided to add a visual counter in the corner that counts down the conversation duration, which also helps users get an idea of how long they can talk to our AI and structure their conversations accordingly.

```ts
const Timer = () => {
  const [timeLeft, setTimeLeft] = useState(() => {
    const sessionTime = getSessionTime();
    return TIME_LIMIT - sessionTime;
  }); // how much time is left for the current session 

  useEffect(() => {
    const interval = setInterval(() => {
      const sessionTime = getSessionTime();
      setTimeLeft(TIME_LIMIT - sessionTime);
    }, 1000); // we update the time counter every second
    return () => clearInterval(interval);
  }, []);

  return (

      {formatTime(timeLeft)} // display formatted

  );
};

// we store session times in cookies
import Cookies from "js-cookie";

const getSessionTime = () => {
  const sessionTime = Cookies.get("sessionTime");

  if (sessionTime) {
    const totalTimeInSeconds = Math.floor(
      JSON.parse(sessionTime).reduce(
        (total: number, session: { start: number; end: number }) =>
          total + (session.end - session.start) / 1000,
        0
      )
    );
    return totalTimeInSeconds;
  }

  return 0;
};
```

In our `Call` component, we’ll add the functionality to update the session end time. We also make our AI say a conversation-ending phrase 10 seconds before the call ends to make the end of the conversation look smooth for the user. After the defined time limit, the conversation ends automatically:

```ts
useEffect(() => {
   const interval = setInterval(() => {
     const time = getSessionTime();
     if (time === TIME_LIMIT - 10) {
       initTimeLimitPhrase();
     }
     if (time >= TIME_LIMIT) {
       endConversation();
       clearInterval(interval);
     } else {
       updateSessionEndTime(); // here we update "sessionTime" cookie with the current time
     }
   }, 1000);
   return () => clearInterval(interval);
 }, [remoteParticipantIds, start]);

const initTimeLimitPhrase = () => {
   daily?.sendAppMessage({
     message_type: "conversation",
     event_type: "conversation.echo",
     conversation_id: conversation?.conversation_id,
     properties: {
       modality: "text",
       text: TIME_LIMIT_MESSAGE,
     },
   }); // after invoking this method, our AI will say the phrase that we defined in TIME_LIMIT_MESSAGE
};
```

In our main component, we’ll start session time after the successful invocation of the start conversation endpoint:

```ts
const handleStartConversation = async () => {
    const conversation = await createConversation();
    setConversation(conversation);
    setSessionStartTime();
    // ...
};

const setSessionStartTime = () => {
 const sessionTime = Cookies.get("sessionTime");
 const arr = sessionTime ? JSON.parse(sessionTime) : [];
 const currentTime = Date.now();
 const expirationMinutes = 60 * 6;
 arr.push({
   start: currentTime,
   end: currentTime,
   exp: (1 / 24 / 60) * expirationMinutes,
 });
 const exp = arr[0].exp;

 Cookies.set("sessionTime", JSON.stringify(arr), {
   expires: exp,
 });
}
```

And, we want to clear session time after the call ends:

```ts
const handleEndConversation = async () => {
    await endConversation(conversation.conversation_id);
    setConversation(undefined);
    Cookies.remove("sessionTime");
};
```

Finally, we render the newly created `Timer` component right inside our `Call` component:

```ts
return (
   <>

   </>
);
```

Now, we can see a timer in the top-right corner of the call:

*Image: The call view with timer visible*

## Insights and wrap up

We’re excited about helping Bolt launch the hackathon website along with this human-AI integration: [check it out for yourself](https://hackathon.dev).

<blockquote class="twitter-tweet" data-dnt="true" align="center"><p lang="en" dir="ltr">holy hell- traffic on <a href="https://t.co/mkP5LON3wo">https://t.co/mkP5LON3wo</a><br><br>and it’s only getting bigger 🤯 <a href="https://t.co/l7utSwT5n9">pic.twitter.com/l7utSwT5n9</a></p>&mdash; Eric Simons (@EricSimons) <a href="https://twitter.com/EricSimons/status/1928252186842054938?ref_src=twsrc%5Etfw">May 30, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Integrating a human AI experience is a great way of bringing freshness to your product, while also increasing its value. And beyond the 24/7 benefits that users get, this is a feature that is sure to have a "wow-effect"!

If you have ideas on your own human AI and you want to integrate it, don't hesitate to reach out to us!

---

**Skip the handoffs** At Evil Martians, we understand how to leverage AI to enhance developer
 tools. Our work with StackBlitz proves it! We helped scale their platform to
 3M+ developers and supported their transformation into Bolt.new, which
 reached $4M ARR in just 4 weeks, and continuing to grow exponentially. Hire
 us to help your dev tool surf the AI wave! [Contact Evil Martians](https://evilmartians.com/contact-us)
