Primitive spec

Daneel

The workflow primitive. Daneel is where the consequences of platform events run — reminders, payment captures, integrations, kitchen routing, comp approvals — with idempotency, retries, compensation, and a durable execution record for every side effect.

Status: DAG engine live. branch, switch, parallel, sleep, wait-for-event, wait-for-signal, and loop control-flow nodes ship; CEL trigger filter; execution modes operator-facing as Wait / Background / Durable (default Background) over the sync / sqs / temporal backends; ad-hoc run + external signal-send endpoints; retry policy; domain step library (fire_lines, serve_line, dispatch_action, route_station). Visual editor in Demerzel is the front-end. Newer: a persisted needs ledger for deferred obligations, and event→outcome capability bindings that wire an event straight to an outcome without a full workflow.

What it owns

Daneel owns workflow definitions and the durable record of their execution. Every named workflow is a sequence of actions with explicit idempotency keys, retry policy, and compensation steps. The execution record is what an operator inspects when something went wrong, and what a workflow uses to resume cleanly after a worker crash.

Daneel does not own outbound communication (Speaker), payment movement (Payments), or scheduling state (Seldon). It calls those primitives as action steps and records what came back.

Concepts

Action
A single named step a workflow can run — speaker.send_message, payments.capture, trantor.release_hold, hober.fire_line, seldon.book_offer, an arbitrary HTTP call to a tenant integration. Actions are idempotent under their Idempotency-Key; the platform retries them on transient failure.
Workflow
A definition that strings actions together with control flow, signals, and timers. Workflows are versioned via Radiant assets; running executions are pinned to the version that started them.
Execution
A live or completed instance of a workflow. Carries the input, every action invocation and result, every signal received, and the terminal status (completed, failed, cancelled, compensated).
Signal
An external input to a running workflow — a manager approving a comp, a courier accepting a delivery, a webhook arriving. Signals are how long-running workflows wait on humans or external systems without busy-polling.
Compensation
The reverse of an action. A workflow that holds Trantor resources, captures Payments, and then fails on a downstream integration will run compensation steps in reverse order — release the Trantor hold, void the Payments authorization — and end in compensated rather than leaving partial state.
Needs ledger
A persisted ledger of deferred obligations. A Need gates a flow until its condition clears — a booking deposit-to-confirm, an Elliot delivery payment-to-dispatch, a manager's approval-to-proceed. The NeedLedger records each obligation; a universal emit-at-time timer expires stale needs; and when a flow is abandoned the NeedCompensator unwinds the satisfied needs in reverse (saga-style). It is the durable backbone behind "don't dispatch until paid" and "don't confirm until the deposit lands," and it surfaces in a Demerzel operator UI as approvals, pending payments, and outstanding obligations.
Capability bindings (event→outcome)
Not everything needs a full DAG. A CapabilityBinding wires a single inbound event directly to an outcome — a capability call from the platform CapabilityRegistry, a Daneel DAG, or an SQS publish — so an operator can express "when this happens, do that" without authoring a workflow. Each firing is recorded as a CapabilityBindingExecution for inspection.
Booking orchestration (staged flows)
Multi-stage booking journeys — book a consult, wait, book the procedure, branch on the result — are composed as a Daneel DAG rather than a bespoke Seldon entity. A seldon.book_offer step handler books an Offer from inside a workflow (resolving offer / availability / anchor / party from config or payload); on a Seldon problem it returns {status: 'unavailable'} instead of throwing, so a downstream branch can run a compensation path. A seldon.cancel_booking handler is its lenient, idempotent saga rollback. Seldon stays single-booking; Daneel's wait and branch nodes own the gaps and conditional stages.
StationRoutingRule
A CEL predicate per kitchen station. When an OrderLine fires, Daneel evaluates rules in priority order and routes the line to the first matching station. Rules live in Daneel; stations live in Trantor as DiscreteResources.
CoursingProfile
Per-Order pacing rules — fire mains when appetizers are served, hold desserts until the table calls for them. Drives the CoursingWorkflow that runs alongside dine-in orders.

API surface

Endpoints are versioned under /daneel/v1/. Most execution starts via subscribed events (Seldon emissions, Hober line transitions, Payments webhooks); the HTTP surface is for workflow management, signal injection, and execution inspection.

Daneel endpoints in the Foundation API reference OpenAPI 3.1 schema for Daneel with request/response shapes, parameters, and a try-it client.
MethodPathPurpose
POST/daneel/v1/executionsStart a named workflow with an input payload.
GET/daneel/v1/executions/{id}Fetch an execution with full action history.
POST/daneel/v1/executions/{id}/signalSend a named signal to a running execution.
POST/daneel/v1/executions/{id}/cancelCancel a running execution; triggers compensation.
POST / GET/daneel/v1/stationsManage station references (thin wrapper over Trantor station Resources).
POST / GET/daneel/v1/stations/{id}/routing-rulesManage CEL routing rules per station, ordered by priority.
POST / GET/daneel/v1/coursing-profilesManage per-Order pacing profiles.
GET/daneel/v1/stations/{id}/queueDerived view: OrderLines fired and ready at the station.
POST/daneel/v1/lines/{lineId}/bumpKDS signal: mark a fired line as ready.
POST/daneel/v1/lines/{lineId}/recallKDS signal: move a ready line back to fired.
POST/daneel/v1/lines/{lineId}/comp-requestStart the CompApprovalWorkflow.

Example: a reminder workflow

A workflow that runs 24 hours before a booking starts:

_type: daneel.workflow
version: 1
name: booking-reminder-24h
trigger:
  event: seldon.booking.confirmed
  delay: -24h relative to anchor.startsAt
steps:
  - id: send-reminder
    action: speaker.send_message
    input:
      templateAlias:     reservation.reminder.v1
      recipientPersonId: "{{ event.party.members[0].terminusPersonId }}"
      urgency:           transactional
      payload:
        bookingId: "{{ event.bookingId }}"
        offerName: "{{ event.offerName }}"
        startsAt:  "{{ event.startsAt }}"
    onError:
      retry:
        maxAttempts: 5
        backoff:     exponential
      onExhausted:
        compensate: false
        alert:      ops

How it fits with the rest

flowchart LR
  OB[(Audit outbox)] --> D(Daneel)
  R[Radiant] -. workflow defs .-> D
  D -- actions --> Sp[Speaker]
  D -- actions --> Pa[Payments]
  D -- KDS routing --> Ho[Hober]
  D -- holds / release --> T[Trantor]
            

Daneel subscribes to events from every primitive and dispatches actions back into them. Seldon lifecycle emissions trigger reminder, capture, and cleanup workflows. Hober line transitions drive kitchen routing. Payments webhook events fan into automation workflows. Speaker is the dispatch target for any workflow that needs to send a message. Radiant stores the workflow definitions, versioned and pinned per execution.