Server Execution SKU
Running workflows as queued jobs, over HTTP or in-process
The server SKU turns the workflow engine into a service: publish workflows, submit jobs with inputs, watch them run, and control them — from a desktop host, a test, or an HTTP client.
Table of Contents
Execution Service
WorkflowExecutionService queues jobs with bounded concurrency and tracks each one through a small state
machine: Queued → Running / Paused → Completed /
Failed / Cancelled.
Publish, submit, wait
using var service = new WorkflowExecutionService(maxConcurrency: 2);
service.Publish(savedWorkflow); // publish once
var job = service.Submit(savedWorkflow.Id, submittedBy: "nightly",
inputs: new Dictionary<string, object> { ["environment"] = "staging" });
service.WaitForCompletion(job.Id, TimeSpan.FromMinutes(5));
Console.WriteLine($"{job.State} in {job.Duration?.TotalMilliseconds:F0} ms");
service.Pause(job.Id); // holds at the next node boundary
service.Resume(job.Id);
service.Cancel(job.Id);
The service also exposes GetJob, GetJobs(state, limit), a JobStateChanged
event for progress UIs, and Dispose which cancels outstanding work.
JSON API Facade
WorkflowExecutionApi wraps the service in a JSON-friendly surface so any transport can expose it. It can
publish a diagram directly, which means a saved layout becomes an executable workflow without writing code:
var api = new WorkflowExecutionApi(service);
var published = api.PublishDiagram(File.ReadAllText("diagram.json"), "Nightly Sync");
var submitted = api.Submit(workflowId, "me", "{\"environment\":\"ci\"}");
var detail = api.GetJob(jobId);
var json = api.ToJson(detail); // the same envelope every call returns
Every method returns an ApiResponse with Success, Error and a JSON-serializable
Payload, so errors are data rather than exceptions.
HTTP Routes
WorkflowRequestRouter maps HTTP requests onto the facade and owns the status codes, so the transport
layer stays trivial and the routing logic stays testable.
| Method | Route | Purpose |
|---|---|---|
| GET | /health | Service status |
| GET | /api/workflows | Published workflows |
| POST | /api/workflows?name= | Publish a diagram DTO (body) as a workflow |
| GET | /api/jobs?state=&limit= | List jobs, optionally filtered |
| POST | /api/jobs?workflowId=&submittedBy= | Submit a job (body = inputs JSON) |
| GET | /api/jobs/{id} | Job detail, including the result |
| POST | /api/jobs/{id}/cancel | pause | resume | Control a running job |
Successful calls return 200, bad input returns 400, and unknown routes or workflows return
404.
Sample HTTP Host
Beep.Skia.Sample.Server is a runnable ASP.NET minimal-API host around the router:
Run the server
dotnet run --project Beep.Skia.Sample.Server -- --demo --url http://localhost:5199
curl -X POST "http://localhost:5199/api/jobs?workflowId=demo" -d "{\"environment\":\"ci\"}"
curl "http://localhost:5199/api/jobs?state=completed"
Options: --url, --concurrency, --demo (publish a built-in workflow),
--publish <file> (publish a saved diagram) and --help.
Deployment Notes
- Transport-agnostic — the service and facade have no HTTP dependency; the ASP.NET host is one adapter among many.
- Concurrency-safe — several jobs run through one engine, and history/job queries are safe while runs are in flight (covered by stress tests).
- Bounded work — concurrency is capped and queued jobs wait rather than piling up.
- Observable — job state changes are events, and every job records start/end times, duration, error text, and the engine result.