Self-hostable, .NET-native automation engine. Build workflows — sequences (and branches) of steps fired by triggers — in a visual builder, run them on a durable engine that survives restarts, and extend everything with plugins. Think Zapier/n8n, but yours: your hardware, your data, your code.
Release history is in CHANGELOG.md. v1 is archived at AutomateX-v1.
The visual builder — switch routing into parallel lanes that rejoin, with continue-on-failure:
The dashboard (execution metrics + per-workflow health) and a live execution run graph:
Every action renders a form from its JSON Schema, with inline {{connections.…}} autocomplete on
config fields:
- Durable engine — each step is a Postgres-backed Wolverine message with per-step retries and backoff; crashes resume from the durable inbox, cron fires via an atomic lease (no double-fires).
- Visual builder — graph + forms generated from each action's JSON Schema, with connection-ref
validation, required-field hints, and inline
{{connections.…}}autocomplete. - Branching & parallel —
switch/gaterouting over an edge-DAG, parallel fan-out lanes that join, continue-on-failure, and try/catch error branches (an "on error →" edge catches a step's failure into a handler lane, with{{steps.<key>.error}}). - Triggers — cron, webhook (per-trigger capability secrets), manual, workflow-chaining,
execution.onFailure(fire a workflow when another fails), and plugin triggers (rss,http.poll,matrix.onMessage). - Actions — built-in
http.request,webhook.send(HMAC-signed),gate,switch,transform(JMESPath),wait,workflow.call,forEach,kv.*,schedule.workflow,llm.prompt,llm.agent,mcp.call; first-party pluginsssh.command,matrix.send,discord.send,slack.send,telegram.send,pushover.send,email.send. - Durable wait & approvals — a
waitstep suspends a run (timer or human approval) into aWaitingstatus that survives restarts; resume from the UI or API, and branch on the decision. Plus "retry from a step" reusing earlier outputs. - Sub-workflows & loops —
workflow.callruns another workflow as a step and waits for its result;forEachmaps a workflow over an array, collecting ordered results. - Failure alerting & metrics — an
execution.onFailuretrigger runs a workflow whenever any run fails (with the failed step + error as{{trigger.payload}}); OpenTelemetry/Prometheus metrics export over OTLP and a/metricsscrape (recipe). - Idempotency keys — give a step a templated key and the engine caches its first success,
skipping re-invocation on re-fires and crash redeliveries;
webhook.sendalso forwards anIdempotency-Keyheader (recipe). - Audit log & instance-admin — an append-only trail of who changed/ran/deleted what (
GET /api/audit+ an Audit page), with an operator role above workspace-owner that sees across workspaces (recipe). - Durable KV store — per-workflow state via
kv.*;setIfAbsent+gategives run-once dedup (recipe). - Encrypted connections — AES-256-GCM secret bundles + OAuth2 connections, referenced as
{{connections.<name>.<field>}}, masked everywhere. Per-workspace data-encryption keys (wrapped by the instance key) with one-click rotation and instance-key re-wrap (recipe). - Workspaces & auth — viewer/editor/owner roles; auth is open → API key → OIDC (with refresh-token sessions).
- Plugin platform — plugins contribute actions, triggers, and connection types, each running
out-of-process in its own sandboxed host (own dependency closure; can't crash or read the
engine); hot-reload, workspace-scoped plugins, and an in-app catalog with sha256-verified installs
(upload gated behind
Engine__AllowPluginUpload). - Self-hosting —
docker compose up, GHCR images onv*tags, and a full homelab guide (Proxmox + Tailscale HTTPS + OIDC) in docs/deploy-homelab.md.
.NET 10 · Aspire 13 · Wolverine (Postgres-backed messaging) · EF Core 10 · FastEndpoints · Postgres · React Router v7 / React 19 / TanStack Query / Tailwind 4
Prerequisites: .NET 10 SDK, Docker (Aspire starts Postgres), Node + pnpm, optionally the Aspire CLI.
dotnet tool restore
aspire run # api + web (Vite) + Postgres — open the "web" resource
dotnet test # engine + module tests (needs Docker via Testcontainers)Web app checks (in src/web): pnpm test && pnpm typecheck.
dotnet publish src/AutomateX -t:PublishContainer # builds the automatex-api image
docker compose up -d
open http://localhost:8080 # UI (8081 = direct API)v* tags publish images to GHCR — point the compose image: entries at
ghcr.io/eiromplays/automatex-api:latest / automatex-web:latest to skip local builds. Running 24/7
on a server? Use docker-compose.prod.yml + .env.example; the full walkthrough (Proxmox, Tailscale
Serve HTTPS, OIDC, updates, backups) is in docs/deploy-homelab.md.
- Plugins: drop
<Name>/<Name>.dllinto the volume-mounted./pluginsand restart the api, or install from the in-app catalog. Seeplugins/README.md. - Auth: configure OIDC, or set
Auth__Bootstrap=trueonce to mint a first instance API key, to gate/api+/hubs. Manage scoped, revocable keys in-app under Settings → API keys. - Encryption:
Encryption__Keydecrypts connection secrets and is never stored — back it up. - Database: migrations apply on startup; the
automatex-postgres-datavolume holds state.
Step configs are templates. {{path}} tokens resolve before each step runs:
{{trigger.payload}} the JSON body sent to the webhook / manual execute call
{{trigger.payload.x.y}} navigate it (object properties + array indices, camelCase)
{{steps.0.output.body}} a prior step's output (0-based order)
{{connections.github.token}} a field from an encrypted connection
{{execution.id}} {{workflow.id}}
A token that is the entire string keeps its JSON type ("{{steps.0.output.statusCode}}" → 200,
not "200"); tokens inside longer strings interpolate. Unresolvable paths fail the step immediately
— no retries, the error names the segment that broke.
public sealed record GreetConfig(string Name);
public sealed record GreetResult(string Greeting);
[Action("greet.hello", "Greet", Description = "Says hello.")]
public sealed class GreetAction : IAction<GreetConfig, GreetResult>
{
public Task<GreetResult> ExecuteAsync(GreetConfig config, ActionContext context, CancellationToken ct = default)
=> Task.FromResult(new GreetResult($"Hello {config.Name}!"));
}Plugins implement IAction<TConfig, TResult> (actions), ITriggerListener<TConfig> (triggers), or
IConnectionType (guided connection types — with IOAuthConnectionType for OAuth2 and
IConnectionTester for a "Test" button) against AutomateX.Plugin.Sdk; config/result types export
as JSON Schema and drive the builder forms. Scaffold one:
dotnet new install ./templates/automatex-plugin
dotnet new automatex-plugin -n MyPluginFirst-party plugins live under src/Plugins; the sample (echo/delay actions) is in
samples/AutomateX.SamplePlugin. Deploy convention: plugins/<PluginName>/<PluginName>.dll with its
dependency dlls in the same folder (a normal dotnet publish/build, or the catalog zip — both
include them); override the root with Engine__PluginsPath.
Plugins run out-of-process (v4.0): each loads in its own AutomateX.PluginHost child with its own
dependency closure, so a plugin can't crash or read the engine. Two rules follow from the boundary:
take services from ActionContext (context.Logger, context.Http) rather than constructor
injection (the host fills only optional ctor params), and note that a plugin can't register engine
event listeners — use a trigger such as execution.onFailure. See
docs/plugin-sandboxing-design.md.
- Deployment: homelab guide
- Recipes: self-deploy · dedup & durable state · transform & webhooks · error handling · approvals & waits · sub-workflows & loops · failure alerting & metrics · idempotency · audit log · key rotation · conditional gate · reminders · jarvis-lite · backups · API keys
- Design notes: branching · trigger → lane routing · named step references · error branches · durable wait · sub-workflows · forEach · OAuth connections · llm.agent · mcp.call · metrics & alerting · idempotency · audit & admin · per-tenant DEKs
See CONTRIBUTING.md for setup and conventions, and SECURITY.md to report a vulnerability privately. Licensed under LICENSE.



