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.

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.

MethodRoutePurpose
GET/healthService status
GET/api/workflowsPublished 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 | resumeControl 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.