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: Scaffolded. Entity and service layout are in place; the workflow runtime (likely Temporal) and the StationRoutingRule / CoursingProfile entities behind kitchen routing are designed and queued. This spec describes the planned shape.

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, 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.
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.

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.