> ## Documentation Index
> Fetch the complete documentation index at: https://lemonslice.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Emotion Engine

> Add life-like behavior to your avatar with Character World Model 1

export const ActionExample = ({name, slug}) => {
  const clipBase = "https://lemonslice.com/cwm-1/actions/";
  const [open, setOpen] = useState(false);
  const dialogRef = useRef(null);
  const triggerRef = useRef(null);
  const close = () => {
    setOpen(false);
  };
  useEffect(() => {
    if (!open) return undefined;
    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    if (dialogRef.current) dialogRef.current.focus();
    const onKey = event => {
      if (event.key === "Escape") {
        event.preventDefault();
        setOpen(false);
      }
    };
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = previousOverflow;
      if (triggerRef.current) triggerRef.current.focus();
    };
  }, [open]);
  return <>
      <button ref={triggerRef} type="button" className="cwm-action-eye" aria-label={"Watch example of " + name} onClick={() => setOpen(true)}>
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M2.2 12S6 5.5 12 5.5 21.8 12 21.8 12 18 18.5 12 18.5 2.2 12 2.2 12z" fill="none" stroke="currentColor" strokeWidth="1.75" />
          <circle cx="12" cy="12" r="2.75" fill="none" stroke="currentColor" strokeWidth="1.75" />
        </svg>
      </button>
      {open ? <div className="cwm-action-modal" onClick={close}>
          <div ref={dialogRef} className="cwm-action-dialog" role="dialog" aria-modal="true" aria-labelledby="cwm-action-dialog-title" tabIndex={-1} onClick={event => event.stopPropagation()}>
            <div className="cwm-action-dialog-bar">
              <code id="cwm-action-dialog-title">{name}</code>
              <button type="button" className="cwm-action-close" aria-label="Close example" onClick={close}>
                <svg viewBox="0 0 24 24" aria-hidden="true">
                  <path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
                </svg>
              </button>
            </div>
            <video src={clipBase + slug + ".mp4"} poster={clipBase + slug + ".webp"} autoPlay muted loop playsInline controls />
          </div>
        </div> : null}
    </>;
};

LemonSlice avatars always have natural, expressive hand gestures when they speak. **Character World Model 1** includes a built-in emotion engine that decides what the character does and feels. Use a preset to give the avatar a more life-like presence, or trigger specific actions and emotions programmatically.

<Info>
  CWM-1 is available to users on Ultra and Enterprise tiers. See [pricing](https://lemonslice.com/pricing).
</Info>

<img className="cwm-1-hero" src="https://mintcdn.com/lemonslice/W1IFvL-zAr3MFZbP/images/cwm-1-hero.webp?fit=max&auto=format&n=W1IFvL-zAr3MFZbP&q=85&s=bdc8b9f2eb7d1bd5091679775e56852c" alt="Looping CWM-1 examples: any character, whole-body motion, collar adjustments, and hair interactions" width="1360" height="508" data-path="images/cwm-1-hero.webp" />

***

There are three ways to use actions and emotions, from fully automatic to fully manual.

## 1. Action Machine (automatic)

Get the full power of actions with no custom logic required. LemonSlice automatically selects and triggers contextually appropriate actions based on the conversation state.

Set `model` and `action_engine` when creating a session:

<CodeGroup>
  ```python LiveKit {3-4} theme={null}
  avatar = lemonslice.AvatarSession(
      agent_image_url="https://example.com/avatar.jpg",
      model="cwm-1",  # or "cwm-1-lite"
      action_engine="natural",
  )

  session_id = await avatar.start(session, room=ctx.room)
  ```

  ```python Pipecat {8-9} theme={null}
  async with aiohttp.ClientSession() as session:
      transport = LemonSliceTransport(
          bot_name="Pipecat",
          api_key=os.getenv("LEMONSLICE_API_KEY"),
          session=session,
          session_request=LemonSliceNewSessionRequest(
              agent_image_url="https://example.com/avatar.jpg",
              model="cwm-1",  # or "cwm-1-lite"
              action_engine="natural",
          ),
      )
  ```

  ```json Agora {13-14} theme={null}
  // Given Agora’s Conversational AI join endpoint
  // with an avatar block under properties,
  // add the parameters model and action_engine
  {
    "avatar": {
      "vendor": "generic",
      "enable": true,
      "params": {
        "api_key": "<lemonslice_api_key>",
        "api_base_url": "https://lemonslice.com/api/liveai/agora",
        "avatar_id": "lemonslice",
        "agent_image_url": "<public_image_url>",
        "model": "cwm-1", // or "cwm-1-lite"
        "action_engine": "natural",
        "video_encoding": "H264",
        "agora_uid": "<avatar_rtc_uid>",
        "agora_token": "<avatar_rtc_token>"
      }
    }
  }
  ```

  ```bash WebSocket {9-10} theme={null}
  # model: "cwm-1" or "cwm-1-lite"
  curl --request POST \
    --url "https://lemonslice.com/api/liveai/sessions" \
    --header "X-API-Key: $LEMONSLICE_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
      "transport_type": "websocket-livekit",
      "agent_image_url": "https://example.com/avatar.jpg",
      "model": "cwm-1",
      "action_engine": "natural",
      "livekit_properties": {
        "livekit_url": "wss://your-project.livekit.cloud",
        "livekit_token": "<agent_access_token>"
      }
    }'
  ```
</CodeGroup>

#### Enrich the Action Machine with user state (LiveKit only)

By default, the Action Machine knows two avatar states: **IDLING** and **RESPONDING**. You can unlock a third state, **LISTENING**, by publishing the user's speech state from your agent code (e.g., `agent.py`). This lets the avatar react naturally while the user is speaking.

```python theme={null}
import asyncio
import json

USER_STATE_TOPIC = "ls.user-state"
LEMONSLICE_AVATAR_IDENTITY = "lemonslice-avatar-agent"

async def _publish_user_state(state: str) -> None:
    try:
        await ctx.room.local_participant.publish_data(
            json.dumps({"state": state}).encode("utf-8"),
            reliable=True,
            topic=USER_STATE_TOPIC,
            destination_identities=[LEMONSLICE_AVATAR_IDENTITY],
        )
    except Exception:
        logger.exception("failed to publish user state")

@session.on("user_state_changed")
def _on_user_state_changed(ev: UserStateChangedEvent) -> None:
    if ev.new_state == "speaking":
        state = "speaking"
    elif ev.new_state == "listening":
        state = "idle"
    else:
        return
    asyncio.create_task(_publish_user_state(state))
```

***

## 2. LiveKit data messages (manual, lowest latency)

Trigger actions yourself with full control over timing. LiveKit data messages are approximately 250 ms faster than the REST control endpoint, making them the recommended option for LiveKit users.

Set `model` to `"cwm-1"` when creating a session:

```python {3} theme={null}
avatar = lemonslice.AvatarSession(
    agent_image_url="https://example.com/avatar.jpg",
    model="cwm-1",  # or "cwm-1-lite"
)

session_id = await avatar.start(session, room=ctx.room)
```

Publish a data message with topic `ls.action` and a JSON payload containing the action name from the [available actions](#available-actions) table:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const LEMONSLICE_ACTION_TOPIC = "ls.action";
  const LEMONSLICE_AVATAR_IDENTITY = "lemonslice-avatar-agent";

  const performAction = useCallback(
    async (action: string) => {
      const avatarParticipant = remoteParticipants.find(
        ({ identity }) => identity === LEMONSLICE_AVATAR_IDENTITY,
      );

      if (!avatarParticipant) {
        console.warn("LemonSlice avatar is not in the room");
        return;
      }

      const payload = new TextEncoder().encode(JSON.stringify({ action }));

      try {
        await room.localParticipant.publishData(payload, {
          reliable: true,
          topic: LEMONSLICE_ACTION_TOPIC,
          destinationIdentities: [avatarParticipant.identity],
        });
      } catch (error) {
        console.error("Failed to perform LemonSlice action:", error);
      }
    },
    [room, remoteParticipants],
  );
  ```

  ```python Python theme={null}
  LEMONSLICE_ACTION_TOPIC = "ls.action"
  LEMONSLICE_AVATAR_IDENTITY = "lemonslice-avatar-agent"

  async def _perform_action(action: str) -> None:
      try:
          await ctx.room.local_participant.publish_data(
              json.dumps({"action": action}).encode("utf-8"),
              reliable=True,
              topic=LEMONSLICE_ACTION_TOPIC,
              destination_identities=[LEMONSLICE_AVATAR_IDENTITY],
          )
      except Exception:
          logger.exception("failed to publish avatar action")
  ```
</CodeGroup>

***

## 3. Control endpoint (manual, any integration)

Use the [control endpoint](/docs/api-reference/control-session) to trigger actions from any integration (Daily, Agora, WebSocket). If [LiveKit data messages](#2-livekit-data-messages-manual-lowest-latency) are available to you, prefer those for lower latency.

Set `model` to `"cwm-1"` when creating a session:

<CodeGroup>
  ```python Pipecat {8} theme={null}
  async with aiohttp.ClientSession() as session:
      transport = LemonSliceTransport(
          bot_name="Pipecat",
          api_key=os.getenv("LEMONSLICE_API_KEY"),
          session=session,
          session_request=LemonSliceNewSessionRequest(
              agent_image_url="https://example.com/avatar.jpg",
              model="cwm-1",  # or "cwm-1-lite"
          ),
      )
  ```

  ```bash WebSocket {9} theme={null}
  # model: "cwm-1" or "cwm-1-lite"
  curl --request POST \
    --url "https://lemonslice.com/api/liveai/sessions" \
    --header "X-API-Key: $LEMONSLICE_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
      "transport_type": "websocket-livekit",
      "agent_image_url": "https://example.com/avatar.jpg",
      "model": "cwm-1",
      "properties": {
        "livekit_url": "wss://your-project.livekit.cloud",
        "livekit_token": "<LIVEKIT_TOKEN>"
      }
    }'
  ```
</CodeGroup>

Once the session is running, send a `POST` request with the action name. Replace `{session_id}` with your active session ID and `<ACTION_NAME>` with any action from the [table below](#available-actions).

```bash {4} theme={null}
curl -X POST "https://lemonslice.com/api/liveai/sessions/{session_id}/control" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $LEMONSLICE_API_KEY" \
  -d '{"event": "action", "action": "<ACTION_NAME>"}'
```

<Warning>
  When using `model="cwm-1"`, the arguments `agent_prompt` and `agent_idle_prompt` are ignored.
</Warning>

***

## Available actions

Actions marked with ✅ work best when the avatar is silent. All other actions can be triggered at any time. Select the eye icon to watch an example.

<div className="cwm-actions">
  <table>
    <thead>
      <tr>
        <th>Action Name</th>
        <th>Description</th>
        <th>Best During Silence</th>
      </tr>
    </thead>

    <tbody>
      <tr>
        <td><span className="cwm-action-name"><code>adjust\_collar</code><ActionExample name="adjust_collar" slug="touch-collar" /></span></td>
        <td>Adjusts shirt collar/necklace</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>angry</code><ActionExample name="angry" slug="angry" /></span></td>
        <td>Angry expression</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>arms\_crossed</code><ActionExample name="arms_crossed" slug="cross-arms" /></span></td>
        <td>Crosses arms over chest</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>blow\_kiss</code><ActionExample name="blow_kiss" slug="blow-kiss" /></span></td>
        <td>Blows a kiss</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><code>clasp\_hands</code></td>
        <td>Clasps hands together</td>

        <td />
      </tr>

      <tr>
        <td><code>clear\_action</code></td>
        <td>Ends the current action</td>

        <td />
      </tr>

      <tr>
        <td><code>deep\_breath</code></td>
        <td>Takes a slow, deep breath</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><code>excited</code></td>
        <td>Excited expression</td>

        <td />
      </tr>

      <tr>
        <td><code>explain</code></td>
        <td>Talking gesture using hands</td>

        <td />
      </tr>

      <tr>
        <td><code>explain\_finger\_up</code></td>
        <td>Raises a finger to make a point</td>

        <td />
      </tr>

      <tr>
        <td><code>explain\_palms\_up</code></td>
        <td>Talking gesture with palms up</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_down</code></td>
        <td>Glances downward</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_down\_then\_rub\_eye</code></td>
        <td>Glances down then rubs eye</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_right</code></td>
        <td>Glances to the right</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_side\_down\_side</code></td>
        <td>Glances to the side then down</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_side\_up</code></td>
        <td>Glances to the side then up</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_sideways</code></td>
        <td>Glances to the side</td>

        <td />
      </tr>

      <tr>
        <td><code>glance\_up</code></td>
        <td>Glances upward</td>

        <td />
      </tr>

      <tr>
        <td><code>hands\_on\_hip</code></td>
        <td>Places hands on hips</td>

        <td />
      </tr>

      <tr>
        <td><code>hold\_chin</code></td>
        <td>Rests hand on chin</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>laugh</code><ActionExample name="laugh" slug="laugh" /></span></td>
        <td>Laughs</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>lean\_long</code><ActionExample name="lean_long" slug="lean-forward" /></span></td>
        <td>Sustained forward lean</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>lean\_slow</code><ActionExample name="lean_slow" slug="lean-forward" /></span></td>
        <td>Slow, gentle lean forward</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>lean\_strong</code><ActionExample name="lean_strong" slug="lean-forward" /></span></td>
        <td>Pronounced lean forward</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>listen</code><ActionExample name="listen" slug="nod" /></span></td>
        <td>Acknowledging nods</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>looking\_around</code><ActionExample name="looking_around" slug="look-around" /></span></td>
        <td>Looks around the room</td>

        <td />
      </tr>

      <tr>
        <td><code>move\_more</code></td>
        <td>Pronounced body movement</td>

        <td />
      </tr>

      <tr>
        <td><code>move\_subtle</code></td>
        <td>Subtle body movement</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>phone\_call</code><ActionExample name="phone_call" slug="phone-call" /></span></td>
        <td>Talks on the phone</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>raise\_eyebrow</code><ActionExample name="raise_eyebrow" slug="confused" /></span></td>
        <td>Raises an eyebrow</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>rub\_eye</code><ActionExample name="rub_eye" slug="touch-eye" /></span></td>
        <td>Rubs one eye</td>

        <td />
      </tr>

      <tr>
        <td><code>rub\_eye\_then\_touch\_hair</code></td>
        <td>Rubs eye then touches hair</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>shoulder\_shimmy</code><ActionExample name="shoulder_shimmy" slug="dance" /></span></td>
        <td>Playful shoulder shimmy</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>smile</code><ActionExample name="smile" slug="smile" /></span></td>
        <td>Warm smile</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>texting</code><ActionExample name="texting" slug="text" /></span></td>
        <td>Texts on the phone</td>

        <td />
      </tr>

      <tr>
        <td><code>tilt\_head\_right</code></td>
        <td>Tilts head</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>touch\_chin</code><ActionExample name="touch_chin" slug="touch-chin" /></span></td>
        <td>Touches chin thoughtfully</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>touch\_hair</code><ActionExample name="touch_hair" slug="touch-hair" /></span></td>
        <td>Touches or caresses hair</td>

        <td />
      </tr>

      <tr>
        <td><code>turn\_left</code></td>
        <td>Turns body to the left</td>
        <td>✅</td>
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>wave</code><ActionExample name="wave" slug="wave" /></span></td>
        <td>Waves hello or goodbye</td>

        <td />
      </tr>

      <tr>
        <td><span className="cwm-action-name"><code>yawn</code><ActionExample name="yawn" slug="yawn" /></span></td>
        <td>Yawns</td>
        <td>✅</td>
      </tr>
    </tbody>
  </table>
</div>

***

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="What happens if I send an action while the Action Machine is running?">
    A manual action takes priority. Publishing `ls.action`, or sending an `action` event to the [control endpoint](/docs/api-reference/control-session), cancels the action the Action Machine is playing and starts yours. When your action finishes, the Action Machine resumes if `action_engine` is set.
  </Accordion>

  <Accordion title="How do I stop the current action?">
    Send `clear_action` on the same channel you use for other actions. It ends the action in progress and returns the avatar to its default behavior.

    LiveKit data message on topic `ls.action`:

    ```json theme={null}
    { "action": "clear_action" }
    ```

    Control endpoint:

    ```bash theme={null}
    curl -X POST "https://lemonslice.com/api/liveai/sessions/{session_id}/control" \
      -H "Content-Type: application/json" \
      -H "X-API-Key: $LEMONSLICE_API_KEY" \
      -d '{"event": "action", "action": "clear_action"}'
    ```
  </Accordion>

  <Accordion title="How long does an action last?">
    Every action plays once for a fixed duration, before the avatar returns to its default state. Trigger the same action again to hold the action or expression longer.
  </Accordion>

  <Accordion title="Can I play two actions at the same time?">
    CWM-1 plays one action at a time, so `angry` and `arms_crossed` cannot run together. Wait for the current action to finish, or send `clear_action` before starting the next one.
  </Accordion>

  <Accordion title="Can I really trigger actions from a single photo?">
    Yes, CWM-1 works with any character. Use of actions or emotions does not require any character-specific fine-tuning.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.