Defining Decisions
A decision is a named, schema-validated LLM call declared in your Product DSL. Rules invoke it with op: decision. The runtime renders a prompt, optionally retrieves vector memory, calls the configured provider, validates JSON output, and emits a domain event.
Defining a decision
# decisions/triage.decisions.causet
decisions:
triage_ticket:
provider: reasoning # key from providers:
prompt: triage_v1 # key from prompts:
emits: TICKET_AI_TRIAGED # event type (must exist in events:)
memories:
- customer_history # optional memory refs
input:
ticket_id:
type: string
required: true
customer_id:
type: string
required: true
subject:
type: string
required: true
body:
type: string
required: true
output:
priority:
type: string
required: true
category:
type: string
required: true
summary:
type: string
required: true
confidence:
type: number
required: true| Field | Required | Description |
|---|---|---|
provider | yes | Logical provider name from providers: |
prompt | yes | Prompt template name from prompts: |
emits | yes | Event type emitted on success (declared in events:) |
memories | no | List of memory names to retrieve and inject into the prompt |
input | yes | Input schema — validated against op: decision bindings |
output | yes | Output schema — validated against LLM JSON response |
The compiler checks that every ref: in op: decision resolves to a decision, that required inputs are bound, and that unknown input keys are rejected.
Invoking with op: decision
# actions/support.actions.causet
actions:
CREATE_TICKET:
state: ticket
entity_id_expr: intent.ticket_id
input:
ticket_id: { type: string, required: true }
customer_id: { type: string, required: true }
subject: { type: string, required: true }
body: { type: string, required: true }
core:
rules:
- name: persist_ticket
then:
- op: set
path: /status
value: open
- op: emit
event_type: TICKET_CREATED
payload:
ticket_id: intent.ticket_id
customer_id: intent.customer_id
subject: intent.subject
body: intent.body
side_effects:
rules:
- name: ai_triage
then:
- op: decision
ref: triage_ticket
input:
ticket_id: intent.ticket_id
customer_id: intent.customer_id
subject: intent.subject
body: intent.bodyop: decision parameters
| Parameter | Required | Description |
|---|---|---|
ref | yes | Decision name (must match a key under decisions:) |
input | no | Map of input field → expression. Required schema fields must be bound. |
op: decision is side_effects only.
The compiler rejects op: decision in preflight and core. External LLM calls are traced in the Timeline but are not replayed when rebuilding state from the ledger — same rule as other side-effect I/O.
Keep entity mutations and the primary domain event in core. Run AI in side_effects after core commits.
What happens at runtime
- Resolve input — evaluate each
input:binding against the execution context (intent.*,entity.*, etc.). - Validate input — reject if required fields are missing or wrong type.
- Retrieve memory — for each entry in
decisions.*.memories, vector-search the partition (e.g.customer_id) and bind results tomemories.<name>in the prompt context. - Render prompt — substitute
{{ field }}and{{ memories.customer_history }}in the prompt template. - Execute — call the provider (OpenAI, Anthropic, or mock). Credentials resolve from BYOK keys configured in the control plane.
- Validate output — parse JSON and check against
output:schema. - Emit event — merge input + output into payload and emit the configured event type.
- Audit — record the step in timeline with executor, model, tokens, and duration.
The emitted event flows through listeners, projections, and webhooks like any other domain event.
Applying AI output to state
Decisions emit events; they do not mutate entity fields directly. Use a listener to apply structured output:
# listeners/triage.listeners.causet
listeners:
- on: TICKET_AI_TRIAGED
priority: 0
mutate:
- op: set
field: priority
value: event.payload.priority
- op: set
field: category
value: event.payload.category
- op: set
field: ai_summary
value: event.payload.summary
- op: set
field: ai_confidence
value: event.payload.confidenceListeners run deterministically in-process when the event is emitted. Projections then materialize read models for agent queues and dashboards.
Event payload shape
The emitted event payload merges:
- All input fields passed to
op: decision - All output fields returned by the LLM (after validation)
Define the event schema in events: to match:
events:
TICKET_AI_TRIAGED:
state: ticket
entity_expr: event.ticket_id
payload:
ticket_id: string
customer_id: string
subject: string
body: string
priority: string
category: string
summary: string
confidence: numberObservability
Each decision execution appears in:
- Timeline — one step per LLM call with input, effects, and compute usage
- Ledger commit — the configured domain event in the emitted events for that cursor
- CU metering — memory retrievals + token usage billed as compute units
Use the control plane Decision Log to debug prompt context, model errors, and validation failures.
See also
- Secrets & Keys — provider API key setup
- Examples — moderation and triage patterns
- Vector Memory — feeding
memories:into decisions - Providers & Prompts — model configuration and templates
- Support Copilot — full triage workflow