# Two agents, one page demo: source

> The code that makes the Two agents, one page demo work, walked through snippet by snippet.

Two different agents on one hotel page. The page does very little to make that work.

## Two agents, one hook

Each desk is its own element with its own agent handle, mounted by the same hook.

```tsx file=src/demos/atlas/index.tsx
const concierge = useDemoElement({
  handle: DESKS.concierge.handle,
  …
})
const spa = useDemoElement({
  handle: DESKS.spa.handle,
  …
})
```

## A session per agent

Each element asks for a session bound to its own agent, so the two never share a conversation.

```ts file=src/demos/shared/session.ts
r = await doFetch(`${DEMO_SESSION_PATH}?agent=${encodeURIComponent(handle)}`, {
```

## The spa speaks first

Only the spa has a script. Its opening step greets the guest before they type.

```json file=demos/atlas-spa/script/atlas-spa-welcome.json
"onEnter": [
  {
    "id": "hello",
    "heading": "Greet the guest first",
    "do": {
      "$emit": "Good evening, and welcome to the spa at The Atlas. This is a demo, so the treatments and times are sample data. Before I suggest anything: would you like something restoring, or something to help you sleep?"
    }
  },
  …
```

## Each desk lights on its own events

Each status light follows only that desk's run events.

```ts file=src/demos/atlas/evening.ts
export function deskReducer(state: DeskState, event: AnyEvent): DeskState {
  if (!event || typeof event.type !== 'string') return state
  if (STARTS.has(event.type)) return 'speaking'
  if (ENDS.has(event.type)) return state === 'idle' && event.type === 'run_error' ? 'idle' : 'listening'
  return state
}
```
