Structured chat with DGI
TL;DR
- An attached actor can answer with DGI instead of prose. Set its responder to
dgi(orhybrid) withbuzz.actor.set-responder, and its replies become cards: forms, confirm cards, tables with row actions, charts, KPI tiles, logs, links, live cards and quick replies. The composer gets a/command palette. - Your workflows are the fields. DGI asks for exactly the inputs of a workflow you allow, shows the result as a view, and proposes changes behind a confirm card. You control the shape by authoring the workflow, and the scope with
allowed_workflow_typesandsystem_prompt. - Most turns need no LLM. The typed decision engine, Jev, routes a turn (read, form, proposal, which workflow) with one typed call. When Jev is not confident, DGI falls back to the LLM tool loop. In our testing a Jev fast-path answer took about 3 s and an LLM answer about 10 s.
- Writes need a structured confirm. A change runs only after the person presses Confirm on the card the server stored. A typed "yes" never confirms.
- End users are first-class. In seat mode DGI runs as the person who wrote the message, limited to workflows your app exposes to end users.
- Four ways to use it today: the hosted chat page, the embeddable React component, your own client over the wire contract, or any app or API through the
dgi.chat.*workflows, with no chat space at all. - Status: Alpha. Structured chat is part of DGI, which is Alpha. Staff actors are Beta.
What people see
An actor with a DGI responder answers a chat message in one of these shapes:
| Card | What it does |
|---|---|
| Form | Asks for the typed inputs of a workflow: text, numbers, dates, selects, multiselects, booleans, files. Long forms become a wizard; a form can open as a modal |
| Confirm | Shows the proposed action with its inputs in plain labels. Confirm, Cancel, and Edit when the proposal has inputs |
| Table | Rows from a read, with a toolbar (Refresh, Live) and per-row actions such as Details, Comment or Assign |
| Chart | Bar, line or pie, drawn inline. Numbers come from the read, never from the model |
| KPI tiles | Up to eight tiles with value, unit, delta and trend |
| Logs | A scrollable monospace block with a copy button |
| Link | One button, for example to set up a connection in the console |
| Run | A live card for a workflow run DGI started, updated in place until it ends |
| Composition | A step list of a composition you are editing (organization members only) |
| Select, action list | Pick one option, or act on an item of a list |
| Datagrid | A large result, paged, sorted and filtered by the server, with row actions |
| Detail | One record: badges, sections of fields and related lists |
| DAG, schema, timeline | A workflow diagram, an entity diagram, or a waterfall of steps on a time axis |
| Diff | Before and after, per field. A confirm card for an update shows it inline |
| Query console | A read-only SQL console over AppData, for organization members. It runs only what the person types |
Read cards can also carry view controls (a date range, a status filter, a group-by). Every card, its fields and its answers are in the card catalog.
Under any answer the actor can offer quick replies, and the composer opens a / palette with the actor's commands. See Cards, live updates and proactive posts.
How a turn works
person posts ──► relay ──► bridge ──► platform checks (signature, member, ceiling, dedupe)
│
responder = dgi or hybrid
▼
DGI runs as the message author, scoped to allowed_workflow_types
│
Jev decides (read? form? proposal? which workflow?) ── not confident ──► LLM tool loop
▼
read: run it, build the view write: form, then confirm card
▼
answer posted with a buzz-ui block, signed by the actor's key
▼
person taps a card ──► threaded buzz-ui-response ──► validated against the stored card
The actor still holds its seat and its chat key: it is who signs the answer. DGI supplies what the answer says.
Choose how to deliver it
Four options are available today. They share one backend: the same cards, the same decision engine and the same security checks. Options A, B and C configure an actor with a responder; option D configures a profile.
| A. Hosted chat | B. Embed the component | C. Custom client | D. Any app or API | |
|---|---|---|---|---|
| What you get | The Orkestia chat page on your app's address, with every card built in | <BuzzChat> inside your own React app, with every card built in | Your own UI, reading and writing the wire contract | Four workflows: send a message, get text, one card and chips back |
| Code you write | None | A page that signs the person in and mounts the component | The whole client: relay connection, rendering, answers | Calls to dgi.chat.turn and dgi.chat.respond, and the cards you draw |
| Needs a chat space | Yes | Yes | Yes | No |
| Look and feel | Theme document: brand, colors, layout, flags, copy | Theme document plus slots and your own renderers | Anything | Anything |
| Card support | All renderers, the toolbar, row actions, / palette | All of those, plus uiRenderers to replace or add renderers | What you implement. Unknown renderers fall back to text | What you implement. The card is the same JSON object |
| Availability | Available | Package access on request during early access | Available: the contract is documented here | Available |
| Start here | Option A | Option B | Option C | Option D |
How to choose
- You want it live this week, and the Orkestia chat page fits. Option A. You only run workflows.
- The chat must sit inside your product, next to your own screens. Option B. You keep your routing and layout, and the cards come built in.
- You already have a chat UI, a native app, or a non-React stack. Option C. Every card is a JSON block on an ordinary chat message, so any client can draw it.
- You want the cards without a chat space. Option D. A support widget, a mobile screen or a backend sends a message and draws the card it gets back.
For options A, B and C, configure the actor the same way: Option A walks through it, and the responder reference lists every setting.
Cards outside a conversation
The same cards can also live on a page of their own. A Living Surface is a set of live cards that DGI grows from an intent, keeps current with a heartbeat, and retires when the job is done, with its changes streamed to the browser.
Staff, DGI or hybrid
| Responder | Who answers | Use it when |
|---|---|---|
staff (default) | The actor's own Staff configuration: its model, instructions, skills and MCP servers | Free-form help, long answers, tool loops you designed |
dgi | DGI, with cards | The job is running your workflows: look up, fill a form, approve a change |
hybrid | DGI first. A turn DGI cannot handle structurally goes to the Staff configuration | Most turns are structured, but people also ask open questions |
Security in one paragraph
DGI runs as the message author, never as the actor. In seat mode it can reach only the workflows your app exposes to end users, intersected with the actor's allowed_workflow_types. Every write needs a structured confirm on a card the server stored, answered by the person it was issued to, once, before it expires. Cards render only on messages signed by an attached actor. Read Security model before you put it in front of customers.
Ask your AI assistant
Explain the difference between the staff, dgi and hybrid responders for a chat actor, using https://docs.orkestia.dev/raw/chat/structured-chat.md.
List the actors attached to my chat space with data.buzz.attachment.list and tell me which ones answer with DGI and which workflows each may run.
I want a "Support" actor in my chat that searches and opens tickets with forms and confirm cards. Show me the buzz.actor.set-responder call, and wait for my confirmation before anything runs.
Which workflows of my app are exposed to end users, so I can choose allowed_workflow_types for a seat-mode DGI actor?
For AI agents
| Need | Do this |
|---|---|
| Discover | list_workflow_types(prefix="buzz.actor.") and prefix="data.buzz.". Read get_workflow_schema("buzz.actor.set-responder") before proposing a configuration |
| Read the current setup | data.buzz.attachment.list returns each attachment's responder entry (admin read, people only) |
| Safe to start | data.buzz.space.list, data.buzz.space.get, buzz.space.status, data.buzz.actor.commands, get_workflow_schema on any type you plan to allow |
| Confirm with the user first | buzz.actor.set-responder, buzz.actor.set-reply-principal, buzz.actor.attach, buzz.bridge.sync, buzz.space.publish-chat, buzz.actor.post-view, buzz.message.post with ui |
| Callers | buzz.actor.set-responder needs an organization admin or owner signed in as a person. Agents and API keys are refused: prepare the call, a person starts it |
| Scope | allowed_workflow_types is required for dgi and hybrid, 1 to 50 exact names or prefix.*. Never propose an empty list |
| Never | Treat a typed "yes" as a confirm, or build a confirm card yourself. buzz.message.post refuses confirm cards |
| Without a chat | dgi.chat.profile.save, dgi.chat.turn and dgi.chat.respond. See Option D |
Limits
Chat file access is authenticated to space members. Remaining boundaries: no per-file deletion, complete actor answers, no per-person actor memory, sign-out vs key revoke
Option A: hosted chat with DGI
Step by step, turn on structured chat on the hosted Orkestia chat page. Enable the space, seat and attach an actor, set its DGI responder, choose its reply principal, sync the bridge and republish after upgrades
