Build for a platform
$ dandori build <file.flow> --target temporal|temporal-python|temporal-go|asl|durable|argo|pydantic-graph [--out <dir>]
A build writes the code for one platform, and refuses what that platform cannot do (E050) and a workflow whose one run can outgrow it (E040). Temporal is dandori's main platform.
Temporal (TypeScript)
--target temporal writes workflow.ts, types.ts, activities.ts
(makeActivities(own, transport): the tasks dandori writes, with the ones you write), io.ts (the
Transport), rules.ts (the rules as activities around rulec's TypeScript), runtime.ts,
worker.ts (makeWorker(own)) and client.ts. A rule that says connect under use rule has no
code in rules.ts: its activity is in activities.ts, and sends to the rule's service
(A rule as a service).
- Versions. The workflow's type and task queue carry the
.flow's version (hotel_stay_v1), so a new version runs beside the old one.makeWorker(own, { deployment })also puts the worker on Worker Deployment Versioning, with a hash of the code as its build id, and pins each run to the build it started on. To change the code of a version without it, give the histories of the runs that are going on (client.ts'shistories) toworker.ts'sreplayfirst. - The client.
client.tshasstart(which never reuses a workflow id, since the idempotency keys are made from it),answer(a callback's answer, as an Update the workflow refuses for a callback it does not wait for, or a second time),status(the querydandori.status: the line the workflow waits at, each case's state, and the events it waits for) andsendfor the events. Started with{ searchAttributes: true }, the workflow also keeps the search attributeDandoriCases("pi=requires_capture", …) up to date, so runs can be found by their cases' states. For a workflow that implements a service, it also has a function for each of the service's methods (fulfill,answerPacking), and a type for each message by its name in the.proto(FulfillRequest), as Implement a service says. - Long runs. A
repeat, or aforthat is not parallel, at the top of the flow goes on in a new run (Continue-As-New) at the start of a round once the history is long: 10,000 events, or sooner when the server suggests it (the dev server did past 4,096). It hands on the variables, the round, and the loop's list and what it has yielded. The workflow id stays, and so do the idempotency keys and the ids of callbacks and child workflows; under Worker Deployment Versioning the new run stays on its build. - Timeouts. A task without
timeoutgets as long as the other platforms would give it: 60 seconds forhttp,agentandjev, as an HTTP Task has, 900 forlambda, and no limit of its own for the rest. Every activity the workflow's worker serves heartbeats, so that a worker that went away is noticed within 30 seconds. A rule's activity gets 10 seconds, and is retried when it runs out.
Temporal (Python)
--target temporal-python writes the same for Temporal's Python SDK, as a package named after the
workflow: workflow.py (the workflow, and workflows to give the worker), types.py,
activities.py (make_activities(own, transport)), io.py, rules.py (around rulec's Python),
runtime.py, worker.py (make_worker) and client.py (start, answer, status, and for a
workflow that implements a service, a function for each method, in snake case: answer_packing).
The workflow type, the activities, the callback's Update and signal, the query and the ids are named
as in the TypeScript, so a worker in one language can serve another.
Temporal (Go)
--target temporal-go writes the same for Temporal's Go SDK, as one Go package named after the
workflow, with no go.mod of its own: put it in your module and go get the modules its doc.go
names. Go's import paths are ASCII, so when the workflow's name is not, the package is named after the
.flow file (hotel.ja.flow writes hotel_ja), or else workflow. workflow.go has Workflow,
activities.go the interface OwnTasks of the tasks you write and Activities(own, transport),
io.go the Transport, rules.go the rules as activities around rulec's Go, worker.go
NewWorker(client, own, transport, deployment) and Replay, and client.go Start, Answer,
Send, Status and Histories, and for a workflow that implements a service, a function for each
method (Fulfill, AnswerPacking). Everything on the wire is named as in the TypeScript.
- JSON values. The workflow carries its values as JSON values (
map[string]any,[]any,float64,string,bool,nil), as the Python build does: a struct would make a field that is not there the same as a null, drop the fields it does not know, and fail on an answer of another shape, which the workflow has to see to refuse it.types.gostill has a struct for each record and a string type for each enum, andDecodereads a task's arguments into them; each of your tasks gets its arguments asmap[string]anyand answers any value the SDK can write as JSON. - The rules.
rulec gen <rule> --out <the package's directory>/rulecwrites each rule's Go as a module of its own (rulec/go/holdamount), which yourgo.modrequires and replaces with that directory, asrules.gosays at its top. - The default
Transportusesnet/http, the AWS SDK for Go v2 for Lambda and the AWS APIs, OpenAI's Go client for OpenAI's agents, and Anthropic's Go SDK for Claude's, and a package imports only the SDKs its flow calls through. OpenAI has no Agents SDK for Go; the Go client sends the request Step Functions sends to the Responses API. - Go 1.26. The Temporal Go SDK the code is written against, 1.49.0, asks for Go 1.26, which the
gocommand fetches by itself when yours is older.
AWS Step Functions
--target asl writes the state machine, in ASL with JSONata, and for every rule it calls by Lambda, a
Lambda handler around the Python rulec generates. A rule that says connect is called at its service
by an HTTP Task, which needs a connection and a URL that is HTTPS, and has no Lambda function. The tasks call Lambda functions, HTTP APIs through
EventBridge connections, and AWS services through the SDK integrations; a callback hands on a task
token. Code you write has no place in a state machine, so a task that is neither of these is refused
(E050), and so are the features that only Temporal can keep (on cancel, event, and a method of
the service the workflow implements that asks where a run is).
AWS Lambda durable functions
--target durable writes workflow.ts with makeHandler(own, transport), types.ts, tasks.ts,
io.ts and runtime.ts, and for every rule it calls by Lambda, the same Lambda handler as asl, which
the durable function invokes; a rule that says connect is a step that sends to its service. A task of
your own is a step that runs your code.
Argo Workflows
--target argo writes <workflow>.argo.yaml, a WorkflowTemplate that takes the input as the
parameter input and leaves the outputs in the global parameter dd_output, and caller/: the
program that runs the lambda, http, aws, agent and jev tasks and the rules in the workflow's
containers, with its package.json and Dockerfile. A task of your own is a container of your image
(image). The WorkflowTemplate keeps the flow's variables in global output parameters, and the YAML
starts with a comment that names each variable's parameter.
A task's timeout becomes the pod's activeDeadlineSeconds, which Kubernetes counts from the moment
it takes the pod up, the start of its containers included. A busy cluster can take several seconds to
start a pod, so give a task a timeout that leaves room for that. A pod that ends just as its deadline
passes is marked as past it, and Argo then cannot see the name of the error the task ended with: a
retry on that error does not happen.
pydantic-graph
--target pydantic-graph writes a package with graph.py (graph, and its State and Deps),
types.py, tasks.py (make_tasks(own, transport)), io.py, rules.py and runtime.py. Every
statement is a node, and the return type of each node names where the flow can go, so
graph.render() draws the flow. The run lives in the process that runs it: the waits sleep by
Deps.clock, a callback's answer comes to Deps.callbacks, and pydantic-graph 2.x keeps nothing
anywhere else, so a run the process loses is lost. It suits a prototype, or a short flow inside an
agent.
A dates file's dates and a book's operations build for every platform: a date is a call like a rule's,
and an operation of a book is a Lambda function on Step Functions and a call through the Transport
elsewhere, on the chobo client you give it. Step Functions and Lambda durable functions need the
lambda of a date that is called, and Step Functions the lambda of a book (E050).
Dates and books has what each platform writes for them, and what to put beside
it.
Lambda durable functions, Argo Workflows and pydantic-graph refuse event tasks and a service's
method that asks where a run is too (E050). A workflow that implements a service fills the zero
values protobuf's JSON leaves out of its input, and of a callback's answer that a method of the
service sends, on every platform.
Scenarios and the reference interpreter
$ dandori scenarios <file.flow> [--out <dir>]
$ dandori run <file.flow> --scenario <file.json> [--target reference|asl|temporal|temporal-python|temporal-go|durable|argo|pydantic-graph]
scenarios writes inputs and scripted answers that together take every arm, every handler, every way
a case can move, and lists that are empty, short, and longer than their loop takes. run plays one
through the reference interpreter and prints the calls as the target would make them.
How it is checked says how the builds are held to it.