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
- Add the Estuary Action Manager component
- In Characters, drag your
EstuaryCharacter(or leave empty to auto-use the active character) - In Action Bindings, add entries for each action you want to handle:
| Action Name | On 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
| Method | Return | Description |
|---|---|---|
Name | string | Action name (e.g., "wave") |
Parameters | IReadOnlyDictionary | All parameters |
HasParameter(name) | bool | Check if parameter exists |
GetParameter(name, default) | string | Get string parameter |
GetParameterInt(name, default) | int | Get integer parameter |
GetParameterFloat(name, default) | float | Get float parameter |
GetParameterBool(name, default) | bool | Get 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
- User Management -- Manage player identities
- API Reference: Action Components -- Full API reference