dandori
Write the workflow. Check it. Build it.
A small typed language for workflows that call business rules. A workflow books a hotel stay, reserves the lines of an order, answers a customer's inquiry: it calls APIs and rules, waits, retries, and drives things like a Stripe PaymentIntent from state to state.
Checked before it runs. Types, every arm of every match, every state a payment or an order can be left in when the workflow ends, retries that could repeat a change on the other side, the service of a `.proto` the workflow implements, and how long a run's history can grow on the platform it is built for.
Built for five platforms. Temporal (TypeScript, Python or Go), AWS Step Functions, AWS Lambda durable functions, Argo Workflows and pydantic-graph. What each of them runs is played against one reference interpreter, on every scenario the tests generate.
What dandori does
case pi : PaymentIntent follows payment_intent.payment
held capture_method = manual
held confirmation_method = automatic
external authenticate, settle, expire
refused when refused = true
flow
let quote = hold(room: booking.room, nights: booking.nights)
match quote.handling
review => succeed outcome = awaiting_review
auto => pi <- create_intent(amount: quote.amount, …)
pi <- confirm_intent(intent: pi.id)
on card_declined => pi <- get_intent(intent: pi.id)
Decisions come from outside the workflow
A .flow has no comparison and no arithmetic of its own. It branches only by matching an enum, a
bool, or a value that may be absent, which a rule or a task answered: an API, an agent, Jev, your
own code, a person's approval. A decision that must have no gaps can be written in
rulec, as a table that rulec proves complete and free of overlaps,
and a rule's state machine becomes the type of what the workflow drives, here Stripe's
PaymentIntent.
error[E020]: tests/fixtures/hotel_naive.flow:95:1: the workflow can end here with the case `pi` in requires_payment_method, processing, which is not final (succeeded, canceled are)
95 | succeed outcome = stayed
the run that gets there:
81 quote = hold(…)
84 match quote.handling: auto
84 create_intent: pi starts in requires_confirmation
85 confirm_intent: pi requires_confirmation → requires_capture
90 match pi.status: requires_capture
90 wait until booking.check_out
93 capture_intent: pi requires_capture → processing
`settle` happens on the other side: pi processing → requires_payment_method
95 succeed
The checker follows every way it can go
A first draft of the hotel booking waits until check-out and then captures the payment. The checker follows the transitions of Stripe's PaymentIntent, including the ones that happen on Stripe's side without the workflow's asking, and finds a run that ends with the payment neither settled nor released. Each diagnostic comes with the run that gets there. What it checks
dandori build hotel.flow --target temporal
dandori build hotel.flow --target temporal-python
dandori build hotel.flow --target temporal-go
dandori build hotel.flow --target asl
dandori build hotel.flow --target durable
dandori build hotel.flow --target argo
dandori build hotel.flow --target pydantic-graph
One workflow, built for the platform you run
Temporal is the main platform: dandori writes the workflow, the activities that make its HTTP, AWS
and agent calls, the worker and the client, in TypeScript, Python or Go. The same .flow also builds
for AWS Step Functions, Lambda durable functions, Argo Workflows and pydantic-graph, and the code
dandori writes sends the same requests on each. A build refuses what its platform cannot do.
Build for a platform
task read_inquiry(text: string) -> Reading
agent "Read the text of a customer's inquiry, choose its kind, …"
model "gpt-oss:20b"
effort low
url "http://ollama.internal:11434/v1"
plaintext "The model server is reached only inside the cluster network, which the service mesh encrypts"
timeout 60 seconds
retry 2 times every 10 seconds
Agents read, write and choose
A task can be an agent: a model that gets the task's arguments and gives back a value of the task's type, checked like any other answer. The flow can match that answer, or hand it to a rule to decide. OpenAI's models, Claude, and any Open Responses endpoint (Ollama, vLLM, LM Studio, OpenRouter, …) can be called. Agents
Jev decides, and says how sure
A task can ask TypeSafe's Jev, which writes no text: it answers typed questions, each with how sure it is. The task's answer type is the question, a choice among an enum's values, a place on a scale of them, or yes or no, and an answer less sure than the task asks fails the call with an error the flow handles. How sure is enough for what can be a rule's table. Jev
Due dates counted, stock held
A due date can be a date of koyomi's, which counts business days and is checked on every day of its range, and stock can be a book of chobo's, whose bounds hold in every write. A workflow calls a date as it calls a rule, and holds, posts and voids stock as tasks. A hold is a case, and the checker counts that it may expire before it is posted. Dates and books
Drawn for the person who reviews it
dandori doc draws a workflow: every call, match, wait and loop, with what each call does, where
its errors go, what a case can be after it, and every way the workflow can end. As Markdown, it is a
Mermaid flowchart that GitHub draws in a pull request; as one HTML page, each scenario lights up the
way its run goes. The hotel booking, drawn · Draw a workflow
Status
Early. Not yet: Parallel with different branches, OpenAPI documents in YAML, types made from an
OpenAPI document or a Smithy model (a .proto makes them), protobuf's binary encoding and Connect's
streams, the clients of a service a workflow implements written for other languages by a plugin of
protoc, cases the workflow holds itself, a rule's preconditions checked at
the task that produced the value, runs on AWS and on a production Temporal cluster or Temporal
Cloud, the caller image run against real Lambda, HTTP and AWS endpoints from Argo, and agents run
against OpenAI and Anthropic themselves. The design, the decisions and what is left are in
DESIGN.md, in Japanese; its principles are
on Design. dandori is licensed under either of the Apache License 2.0 or the MIT
license, at your option.