Skip to main content

Action System

AI characters send typed client_action events to trigger animations, gameplay, navigation, sound effects, and UI. The SDK opts into this protocol automatically.

How It Works​

A declared action arrives as structured data, separate from response text:

{"name":"wave","arguments":{},"message_id":"turn-id","chunk_index":0}

The SDK converts it to AgentAction, fires EstuaryCharacter.OnActionReceived, and routes it to registered EstuaryActionManager handlers. Actions fire on arrival and are not synchronized to TTS playback. XML tags in prose never execute.

EstuaryActionManager Component​

The EstuaryActionManager component listens to characters and routes actions to handlers. Add it to any GameObject in your scene.

Inspector Setup​

  1. Add the Estuary Action Manager component
  2. In Characters, drag your EstuaryCharacter (or leave empty to auto-use the active character)
  3. In Action Bindings, add entries for each action you want to handle:
Action NameOn Action (UnityEvent)
wave→ Animator.SetTrigger("Wave")
sit→ Animator.SetTrigger("Sit")
navigate→ MyScript.HandleNavigate

Code Setup​

using Estuary;
using Estuary.Models;

public class CharacterActions : MonoBehaviour
{
[SerializeField] private EstuaryActionManager actionManager;
[SerializeField] private Animator animator;

void Start()
{
// Listen for any action
actionManager.OnAnyActionReceived += HandleAction;
}

void HandleAction(AgentAction action)
{
switch (action.Name)
{
case "wave":
animator.SetTrigger("Wave");
break;

case "navigate":
var target = action.GetParameter("target");
NavigateTo(target);
break;

case "emote":
var type = action.GetParameter("type", "happy");
var intensity = action.GetParameterFloat("intensity", 1f);
PlayEmote(type, intensity);
break;
}
}
}

Dynamic Character Binding​

// Add a character at runtime
actionManager.AddCharacter(newCharacter);

// Remove a character
actionManager.RemoveCharacter(oldCharacter);

// Invoke an action programmatically (same path as AI actions)
actionManager.InvokeAction("wave");

Direct Event Handling​

You can also handle actions directly on EstuaryCharacter without the EstuaryActionManager:

character.OnActionReceived += (AgentAction action) =>
{
Debug.Log($"Action: {action.Name}");
foreach (var param in action.Parameters)
{
Debug.Log($" {param.Key} = {param.Value}");
}
};

AgentAction API​

MethodReturnDescription
NamestringAction name (e.g., "wave")
ParametersIReadOnlyDictionaryAll parameters
HasParameter(name)boolCheck if parameter exists
GetParameter(name, default)stringGet string parameter
GetParameterInt(name, default)intGet integer parameter
GetParameterFloat(name, default)floatGet float parameter
GetParameterBool(name, default)boolGet boolean parameter

Imported skeletal clips​

EstuaryClipPlayer can map typed actions to clips in a glTFast-loaded model. See rigged models and imported clips.

Legacy text cleanup​

The current action protocol delivers typed events, and the server keeps stray action tags out of response text. EstuaryCharacter forwards that text unchanged. If you display historical text containing XML action tags, you can clean it explicitly:

using Estuary.Utilities;

string raw = "Hello! <action name=\"wave\" />";
string clean = ActionParser.StripActions(raw);
// clean = "Hello!"

Configuring Actions on Characters​

Actions are defined in your character's configuration in the Estuary Dashboard. The LLM uses these definitions to know when to emit actions. See Action Protocol for details.


Next Steps​