Orkestia
Blog
Chat

Actors in chat

Put a Staff actor into a chat space. Seat binding, triggers, the reply ceiling, DGI responders, progress lines, the tool trace, quick replies, handoff to a person, several actors in one conversation, and messages an actor starts from outside the chat

A Staff actor can be a member of a chat space. It gets its own chat key held on the server, shows with an actor badge, and answers when someone addresses it. Its model, instructions, tools and budget are its own agent config; the chat only decides when it runs and where the answer goes.

How a reply happens

person posts ──► relay ──► bridge (next to the relay, one connection per actor)
                              │ only mentions, DMs and the actor's channels
                              ▼
                    platform checks: real signed message, author is a member,
                    not a duplicate, under the reply ceiling, actor still eligible
                              ▼
                    the actor runs (its own session, its own tools)
                              ▼
                    answer posted in the thread, signed with the actor's key

The bridge filters on the relay side, so chatter nobody addressed to an actor never starts a run. An actor never answers another actor, and never answers its own messages.

Attach an actor

1. Prepare the actor

  • Hire it and give it an agent config with a model, instructions and budget. See Hire an actor.
  • Declare that it acts as the app. A chat reply in the default mode runs as the actor's end-user seat in this identity app, so the actor's stored definition must name the app (on_invoke.input_data.act_as_identity_app_uuid). Without it, attach refuses with actor_act_as_not_declared. The exception is internal mode.
  • Give it an identity. An actor needs its agent identity before it can hold a seat. If binding fails with ACTOR_IDENTITY_MISSING, mint its token first (see Identity and tokens).

2. Bind it to an end-user seat

start_workflow("identity.end-user.bind-actor", {
  "identity_app_uuid": "<app>",
  "staff_actor_uuid": "<actor uuid>",
  "reason": "Support assistant in the app chat"
})

This takes one paid end-user seat in the app. A person in your organization must run it.

3. Attach it to the space

start_workflow("buzz.actor.attach", {
  "space_uuid": "<space uuid>",
  "staff_actor_uuid": "<actor uuid>",
  "display_name": "Acme Assistant",
  "triggers": {"mention": true, "direct_message": true, "channels": ["<channel uuid>"]},
  "max_replies_per_hour": 30
})

The actor joins the listed channels as a bot, publishes its chat profile, and the output says bridge_sync_required: true.

4. Start or refresh the bridge

start_workflow("buzz.bridge.sync", {"space_uuid": "<space uuid>"})

buzz.bridge.sync runs the bridge next to the relay with the attached actors' keys, and gives the bridge a token that can only deliver chat messages. The token is approved by you, so a person must start it. Run it again after every attach, pause, resume or detach. With no active actors left, it scales the bridge to zero and revokes the token. Pass rotate_token: true to replace the token.

In the console, the Chat → Members → Actors tab (Atores) does steps 3 and 4 and shows each actor's status.

Triggers

TriggerThe actor answers
mention: trueA message that mentions it, in any channel it can see
direct_message: trueEvery message in a DM with it
channels: ["<channel uuid>", ...]Every message in those channels

An actor added to a channel with buzz.channel.set-member, or by a member from the chat page, listens there too but answers only mentions and DMs.

The reply ceiling

max_replies_per_hour (default 60) bounds how much one actor writes. It counts chat replies and messages the actor starts from outside the chat together. A message over the ceiling is recorded as skipped with rate_limited and gets no answer. Every reply spends your organization's model budget, so the ceiling is the first guard; the actor's own budget is the second.

Pause, resume, detach

buzz.actor.set-status with space_uuid, attachment_uuid and status (active, paused, detached). Detaching also removes the actor from the relay. Run buzz.bridge.sync afterwards.

Seat mode: what the actor can use

By default an attachment replies in seat mode. The actor runs as its end-user seat in the app, which means:

  • its tools are only the workflows your app exposes to end users, the same compositions a signed-in person could run. Organization skills and MCP servers are not loaded;
  • it sees data the way an end user of the app would, not the way your organization does;
  • it has no memory of earlier conversations with a person (see Limits).

That is the right shape for a customer-facing assistant. For an assistant that answers your own team with organization tools, use internal mode.

DGI responders: answers with cards

By default an actor answers with its Staff configuration, in text. Set its responder to dgi and DGI answers instead, with forms, confirm cards, tables with row actions, charts, KPI tiles, logs, links and live cards, built on the workflows you allow. hybrid tries DGI first and hands the turns it cannot handle to the Staff configuration.

start_workflow("buzz.actor.set-responder", {
  "space_uuid": "<space uuid>",
  "attachment_uuid": "<from data.buzz.attachment.list>",
  "responder": "dgi",
  "allowed_workflow_types": ["<a workflow your app exposes>"]
})

What changes with a DGI responder:

  • DGI runs as the person who wrote the message, not as the actor's seat, so the actor does not need to declare that it acts as the app (a hybrid actor still does, for its handed-off turns).
  • Writes need a confirm card. A typed "yes" never confirms.
  • Most turns need no LLM: the Jev decision engine routes them. The sections below on progress lines and the tool trace describe Staff answers.
  • An organization admin or owner sets it, and no buzz.bridge.sync is needed. responder: "staff" switches back.

Structured chat is part of DGI (Alpha). Start at Structured chat with DGI, and see the responder reference for every setting.

What the actor is given

For each message, the actor receives the message, the author's display name, the conversation so far (up to 20 earlier messages of the thread or DM, with its own lines marked), the files attached to the message as URLs, and the space's locale. It is told it is replying in a chat, so it answers the person instead of writing a report.

While it works: progress lines

Answers usually take fifteen to thirty seconds. People see:

  • a thinking line under the conversation as soon as they address the actor;
  • after about five seconds, a short progress line that changes as the run moves: preparing, thinking, "checking list orders", writing the answer. It is one message edited in place, at most five updates, never counted as unread, never notified, and removed when the answer arrives.

Answers arrive complete, not token by token.

On the answer: the tool trace

An actor's answer can carry a collapsed "how I got here" panel: the run time, how many tool calls ran and failed, and one row per tool (up to eight) with its count and duration. It shows tool names and numbers only. Arguments, outputs, error messages and prompts are never on the message. "No tool was used" is shown as such.

Quick replies

An actor may offer up to five short options (48 characters each) under its answer. Tapping one posts the label as the person's own message in the same thread, so it works exactly like typing it. The buttons show only on the newest message of the thread and disappear once the person writes. A client that ignores them loses nothing: the answer stands on its own.

Handing off to a person

When the actor cannot help, the conversation reaches your team instead of ending.

  • The ask. A person writes a line such as "talk to someone", "talk to a human", "falar com alguém" or "hablar con una persona" (English, Portuguese and Spanish are all recognized, accents and case ignored), or taps the "talk to someone" option an actor offers.
  • The ticket. Instead of running the actor, the platform opens one ticket per conversation in your organization's Tickets, labelled chat and chat:handoff, severity high. It carries the space, channel, thread, who asked, which actor, and the excerpt the actor saw. Asking again in the same conversation adds a comment to the same ticket.
  • The team. The first time, it also raises the Staff event chat.incident.raised, so an actor you subscribed to that event (for example a support manager) wakes up.
  • The confirmation. The actor posts in the thread that a person was asked, with a short ticket reference.

The person never gets access to your tickets; the handoff runs with the space's organization. If an actor's run fails or comes back empty, its reply says it could not answer (with a short code) and offers the "talk to someone" option, so a failure is never a dead end.

Several actors in one conversation

You can attach more than one actor to a space. The platform keeps them polite:

SituationWho answers
A message mentions two actorsBoth, as two separate replies in the thread
A message mentions one actorOnly that one, even inside another actor's thread
An unaddressed reply in a threadThe actor whose answer it replies to
An actor writesNo actor answers it, so actors never loop
Several actors see a handoff requestOne ticket, one confirmation

Each actor is told the names of the other actors in the space and which ones the message addressed.

Messages an actor starts

An attached actor that runs for another reason (a schedule, an event, another workflow) can write first, signed with its own key.

WorkflowInputsDoes
buzz.actor.notifyspace_uuid, target, content, mention, as_actor_uuidThe simple one. target is #channel, a channel name, @Display Name, a display name or a member uuid. A person gets a DM (opened if needed), a channel gets a post. Ambiguous or unknown names fail with a short list of candidates
buzz.actor.dm-openspace_uuid, member_uuid (the actor's member), participant_member_uuids (1 to 8)Opens or reuses a DM and returns its channel_id
buzz.message.postspace_uuid, member_uuid (the actor's member), channel_id, content, reply_to, thread_root, mentions, uiPosts in a channel or DM, optionally in a thread and with mentions. ui adds a card: a read view, or a form for one person. See Proactive cards
buzz.actor.post-viewspace_uuid, attachment_uuid, channel_id, workflow_type, input, kind, livePosts a refreshable read card from one read in the actor's DGI scope. Schedulable. Needs a DGI responder
start_workflow("buzz.actor.notify", {
  "space_uuid": "<space uuid>",
  "target": "#orders",
  "content": "3 orders are waiting for confirmation"
})
CallerMay speak asWhere
The actor itself (its own session)Only itself, and only while its attachment is activeChannels it is in
A person in your organization, or an integration keyAny attached actor of the space, passing as_actor_uuid to notifyThe actor's channels, or any open channel
An end user, or an agent that is not a Staff actorNobody

These posts count against the actor's reply ceiling. To let an actor use them, give it a workflow skill for the type, for example a skill bound to buzz.actor.notify. They are not available inside a seat-mode chat reply, which runs as an end user.

Watching what actors do

Two reads, both for organization admins and not available to agents:

ReadShows
data.buzz.attachment.list with space_uuidEach attached actor: name, triggers, status, reply ceiling, replies in the last hour, and its responder (Staff, or DGI with its settings)
data.buzz.inbound.list with space_uuid, and optionally status, staff_actor_uuid, limitEvery message that woke an actor, newest first: dispatched, replied, skipped or failed, with a reason code. No message content is stored or returned

Reason codes you will see: rate_limited, duplicate, actor_to_actor_ignored, addressed_to_other_actor, thread_owned_by_other_actor, handoff_requested, actor_act_as_not_declared, and actor_<code> when the actor's seat is no longer eligible. The console's Activity tab (Atividade) shows the same ledger.

Ask your AI assistant

prompts
Which actors are attached to my chat space and what wakes each one? Use data.buzz.attachment.list.

Show the last 20 messages that woke actors in my chat space with data.buzz.inbound.list, grouped by status and reason code.

Attach the Staff actor "<actor name>" to my chat space so it answers mentions and DMs, with at most 30 replies per hour. Check that it is bound to an end-user seat first with identity.end-user.actor-binding.get, show me every call, and remind me to run buzz.bridge.sync afterwards.

As the actor "<actor name>", send "Your weekly report is ready" to the #reports channel of my chat space with buzz.actor.notify. Confirm before sending.

For AI agents

RuleDetail
OrderSeat binding (identity.end-user.bind-actor), then buzz.actor.attach, then buzz.bridge.sync. The last two need a person
Check the seatidentity.end-user.actor-binding.get before attaching
Speaking firstAs an actor, call buzz.actor.notify with a target name. Do not pass organization_uuid or as_actor_uuid for yourself
CeilingA rate_limited failure means the actor spent its hour. Wait; do not retry in a loop
No contentThe inbound ledger has no message text. Do not claim to read a conversation from it
HandoffEnd users never call ticket.*. The platform opens the ticket
DGI responderbuzz.actor.set-responder needs an admin or owner signed in as a person, and a non-empty allowed_workflow_types. Prepare the call; do not start it as an agent