Structured chat security model
A chat actor answered by DGI can start your workflows, and in seat mode it answers your customers. These are the rules that keep that safe. They are enforced on the server, on every message, whatever client draws the cards. The same rules hold for dgi.chat.* outside the chat, where the caller's own token decides the principal. Structured chat is part of DGI and is Alpha.
1. DGI runs as the person who wrote the message
| Reply principal | DGI runs as |
|---|---|
seat (default) | The author's end-user account in your app. Every workflow DGI starts is scoped to that end user, exactly as if they had started it from your app |
organization (internal mode) | The author's linked organization account, while it is still a member. If there is none, the admin who turned internal mode on, re-checked as admin on every reply. Every gate of internal mode is re-checked first |
The principal is decided by the platform from the chat's own records, never from anything in the message or the run input. DGI never runs as the actor: the actor only signs the answer.
2. The catalog is locked down
In seat mode, DGI can see and start only:
- workflows marked eligible for end users, and compositions your app exposes to end users,
- intersected with the actor's
allowed_workflow_types.
Compose, save, connection and run-inspection tools are off. The check happens again when a workflow is started, not only in what the model is shown, so a model that names another workflow is refused. In organization mode, allowed_workflow_types is the whole scope, and an attachment with no list refuses to answer (dgi_scope_required) rather than run unscoped.
3. Every write needs a structured confirm
A workflow that changes something runs only when all of these hold:
- the person pressed Confirm on a confirm card, which sends a structured answer threaded under that card;
- the card is one the server stored, for that conversation, not answered before and not expired;
- the proposal it runs is the server's own copy: the workflow and a hash of its exact inputs. Values sent with the confirm are ignored;
- if the actor limits who may confirm (
confirm_allowed_end_user_uuids), the person is on that list.
A typed "yes", "sim" or "ok" never confirms. A confirm=true decided by the model is ignored. Jev, the decision engine, can route a turn but can never produce a confirmation. Changing a value with Edit produces a new proposal with a new hash, on a new card.
4. Cards cannot be forged or replayed
- Only actors draw cards. Clients render a card only on a message signed by an attached actor's key. The same block from a person is shown as text.
- Answers are checked against the stored card: the card exists in this space and channel, was issued to this author, offers this action, has the fields and options the answer uses, and has not expired.
- Forms and confirms are single-use. They are consumed once, under a lock. A redelivered copy of the same answer is recognized, not run twice.
- A refused answer consumes nothing, so the right person can still answer.
- Blocks written by the model are removed. The only card on an actor's message is the one the server built from DGI's structured output.
5. Read cards stay inside their scope
- A refresh or live update re-runs only a read, with the stored inputs, as the stored principal re-checked at that moment. It is refused once the read leaves the actor's scope.
- Row ids and workflow names stay on the server. The card carries labels only, and a row action runs as the person who pressed it, through the same confirm rules.
- Links are posted only for https addresses on the organization console. Run cards are drawn only for runs DGI started in that conversation, in your organization, and show states and timing, not data.
6. Nothing secret travels in the chat
Cards carry no secrets and no platform ids. Vault references are replaced before posting, context chips with an id in them are dropped, and composition inputs named like a secret are removed. File fields accept only URLs on the space relay's own media host.
7. Numbers come from data
Charts and KPI tiles are computed from the full result of the read, not written by the model. When DGI cannot find a value in the read's result, it drops that tile or series, and drops the view when nothing is left.
8. You pay for your own model use
The LLM path uses your organization's AI provider configuration, and Jev uses your organization's TypeSafe connection. The actor's reply ceiling (max_replies_per_hour) still bounds every answer, card answers included.
Audit
- Every message that reached an actor is a row in
data.buzz.inbound.list, with its outcome and reason code, and no message content. - Every DGI run started for a chat reply is a workflow run you can inspect, with the author recorded on it, and every workflow DGI starts carries the same principal.
- Responder changes are recorded on the space with who made them and when.
What this model does not cover
- Messages are not end-to-end encrypted, and Orkestia operates the relay. See Limits.
- The organization mode boundary is the tool list. Keep internal actors on reads and a few narrow writes.
- Your workflows are the boundary of what DGI can do. A workflow you allow and expose to end users is one they can reach through chat, with the same checks as anywhere else. Review what you allow.
Ask your AI assistant
Explain in plain words why a typed "yes" cannot confirm an action in Orkestia structured chat, using https://docs.orkestia.dev/raw/chat/structured-chat-security.md.
List the workflows my seat-mode DGI actor can actually reach: intersect its allowed_workflow_types from data.buzz.attachment.list with what my app exposes to end users.
Review my chat actors' responder settings and flag any that allow writes without confirm_allowed_end_user_uuids where I might want one.
For AI agents
| Rule | Detail |
|---|---|
| Confirms | Only a structured answer to a stored confirm card confirms. Never describe prose as a confirmation, and never try to build one |
| Scope | Effective scope = what the author may run intersected with allowed_workflow_types (seat mode adds end-user eligibility) |
| Principal | DGI runs as the author, not the actor. Do not assume actor permissions in answers |
| Honesty | Do not claim end-to-end encryption. Do not claim the model computes chart numbers |
Cards, live updates and proactive posts
Refresh a read card, keep it live, act on table rows, use the / command palette, run cards, and post cards from outside the chat with buzz.actor.post-view and buzz.message.post
Option D: any app or API (dgi.chat)
Put DGI's structured chat in any web app, mobile app, support widget or backend with four workflows. Save a profile, send a turn, answer the card, and read the card contract as JSON Schema. Jev first, the LLM off by default, and every write behind a stored confirm
