Skip to main content

Actors

Who the agent is talking to — the end user on the other side of a session, keyed by whatever your application already calls them.

Overview

An actor is the identity a generation is attributed to. It carries the persona instructions composed into that turn's prompt and, when you ask for one, a memory container that follows the actor from session to session.

An actor is keyed by your own external_id: a user id, an account number, a hashed email — whatever your system already uses. POST /v1/projects/{project_id}/actors is idempotent on that key, so the first thing your backend does when a new user appears can be this call. No read first, and a retry returns the actor you already have rather than a second one.

Pass the id you get back as actor_id when you open a session, and every turn in that session is attributed to that person.

Channels bring their own

Actors are for traffic that arrives through this API. A conversation that arrives through a channel resolves to an address instead, and the address mints and owns the actor it speaks as. You can read those actors here, but you erase one through its address — see Two ways in, one identity.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Actors.

Data Model

Actor

FieldTypeDescription
idstring, nullablePublic actor ID.
project_idstring, nullableThe owning project.
external_idstring, nullableYour own key for this person.
namestring, nullableA human-friendly name, for reading a transcript later.
instructionsstring, nullablePersona instructions composed into the agent's prompt for this actor's turns.
has_memorybooleanWhether a memory container is linked, carrying what the agent remembers across sessions.
created_atstring (date-time), nullable
updated_atstring (date-time), nullable

Key Concepts

external_id is the idempotency key

Creating an actor converges on external_id: the first call creates one and answers 201, and every later call with the same key returns that same actor with 200. You never have to check first, and a retried request cannot produce a duplicate.

That also means you never need to store naturali's id if you would rather not — GET /v1/projects/{project_id}/actors?external_id=… resolves your own key to an actor without creating anything.

external_id is not updatable. It is the value your references converge on, so moving it would orphan every id you hold while freeing the old key for someone else.

Two ways in, one identity

Both entry points end at the same kind of identity, but each owns the actors it creates:

Channel trafficThis API
Identity resourceAddressActor
Keyed byThe channel identifier (whatsapp:dm:<phone>)Your external_id
Actor minted byThe address, lazily on first messagePOST …/actors
Erased throughDELETE /v1/projects/{project_id}/addresses/{identifier}DELETE /v1/projects/{project_id}/actors/{actor_id}

Two rules keep them from colliding. An external_id may not start with a channel prefix (whatsapp:, discord:, …) or address: — those name actors that belong to an address, and claiming one would put a single identity under two owners. And DELETE /v1/projects/{project_id}/actors/{actor_id} answers 409 for an actor an address owns: erase it through the address, which removes its conversations too.

Reads are not restricted either way. Resolving the actor_id you see on a channel conversation is exactly what GET /v1/projects/{project_id}/actors/{actor_id} is for.

One human can still be two actors

Someone who talks to your agent on WhatsApp and in your app has two identities: one the address minted, one you created. Nothing joins them, so memory does not carry across and erasing one does not erase the other.

That is deliberate. Without a graph saying two identities are the same person, naturali does not know that they are and does not claim to — the same reason address erasure is a promise about one address rather than about a human everywhere.

Erasure is per identity

DELETE /v1/projects/{project_id}/actors/{actor_id} removes the actor and the sessions it holds. It is the erasure path for an end user who reached you through this API, and it is narrower than "erase this human everywhere" for the reason above.

Examples

Identify a user the first time your backend sees them — safe to call on every sign-in, not just the first:

naturali create-actor \
--project-id proj_V1StGXR8Z5jdHi6B \
--external-id user_42 \
--name Ana

Give the actor a persona and a memory that follows it across sessions:

naturali create-actor \
--project-id proj_V1StGXR8Z5jdHi6B \
--external-id user_42 \
--name Ana \
--instructions 'Speaks Portuguese. Prefers short answers.' \
--memory

Attribute a session to the actor, so every turn is recorded against that person:

naturali create-session \
--project-id proj_V1StGXR8Z5jdHi6B \
--agent-id agent_V1StGXR8Z5jdHi6B \
--actor-id actor_V1StGXR8Z5jdHi6B

Resolve your own key to an actor without creating one:

naturali list-actors \
--project-id proj_V1StGXR8Z5jdHi6B \
--external-id user_42

Erase an actor and the sessions it holds:

naturali delete-actor \
--project-id proj_V1StGXR8Z5jdHi6B \
--actor-id actor_V1StGXR8Z5jdHi6B