Features
What SwitchTender does today. Almost all of it runs on Community, which needs no license. Six capabilities are licensed, and the rows below say so where they appear: directory sign-in is Pro, and the full policy engine, the period change register, distributed workers, creating a new PostgreSQL schema, and one-click drift reconcile are Team. The number of approval policies is licensed too: one on Community, five on Pro, and any number on Team. Tiers are at https://switchtender.com/pricing.
| Capability | What you get |
|---|---|
| Runs | Submit over HTTP, real ansible-playbook underneath, human log plus a structured event stream, per-task drill-down with stdout, stderr, rc, and diff. |
| Multi-tool | Ansible, Terraform, OpenTofu, Bash, PowerShell, Python, and Go through one engine, one UI, and one audit trail. Ansible check mode and a Terraform or OpenTofu plan preview a change. A dry run of a Bash, PowerShell, Python, or Go script only checks the script without running it, so it shows nothing about what the script would change. |
| Managed Ansible runtime | switchtender ansible install builds a Python virtual environment beside the database and installs a pinned ansible-core release into it, checking every file pip installs against a hash the binary carries, so a server with the binary and a supported python3 can run Ansible. Runs, inventory resolution, and doctor use it with no restart, ahead of the Ansible on the PATH, and an offline install reads a directory of wheels (Managed Ansible runtime). |
| Live view | Server-Sent Events paint the host matrix and log as the run executes. |
| Splits | Shard an inventory across parallel slices, merged back into one matrix. Hosts packed by measured duration from past runs. |
| Shard retry | Retry a finished split and only its failed shards run again, lineage recorded. |
| Pipelines | Ordered steps or a dependency graph with parallel branches. Failures skip exactly their dependents, per-step retry budgets, set_stats outputs flow to dependent steps as extra vars. |
| Workflow editor | A drag-and-drop canvas at Workflows builds the dependency graph in the browser: draft persistence, undo and redo, full keyboard editing, cycle refusal, and a pan, zoom, and fit-to-view viewport that stays smooth on a large graph, submitted as a pipeline. |
| Workers (Team) | Point switchtender worker at the same database and it competes for queued runs. Leases, heartbeats, and a janitor make dead workers safe. |
| Scheduling | Cron or RFC 5545 recurrence schedules fire runs, splits, or pipelines with full history per fire. A recurrence says what cron cannot, such as the last Friday of each quarter. |
| Fleet memory | Failure rankings, flaky-host detection, outcome sparklines, per-host history, task duration trends, all from persisted structured events. |
| Recovery | Cancellation across processes recorded as canceled, orphaned runs interrupted by lease expiry, terminal saves retried. |
| Storage | SQLite by default. The same flag takes a PostgreSQL DSN for multi-instance. Creating a new PostgreSQL schema needs Team; opening one that already exists is never gated. |
| Projects | Playbooks sourced from git with clone-or-fetch sync. Every run records the exact commit it executed. |
| Templates | Saved launch presets bundling project, playbook, credentials, shards, and extra vars. One click or one POST launches. |
| Auth | User accounts with admin, operator, and viewer roles enforced per route. Bearer tokens hashed at rest. A public bind with no tokens mints an initial admin token at startup and hands it over once, printed on a terminal and otherwise written to a file only the server's account can read, so the API is never served open on a network interface; only loopback and read-only may run open, with a warning. |
| Approvals | Mark a run to require sign-off, or require it automatically by policy on tool, command, target, assessed risk, or who is asking. A run an agent token asks for is held with no policy written, its dry runs included, and its Terraform or OpenTofu apply is held twice, before it plans and again for the apply its plan proposes (Agent runs are held by default). Community holds one such policy, and matching on risk or on who is asking is part of the full engine, which is Team. A held run never executes until an admin approves or rejects it. The decision is a chain entry committing a digest of the exact spec released, the executor refuses a spec that changed after the decision, and the receipt proves the approved and executed change are the same. An approver can give a reason, up to 1,000 characters, masked for known secrets and confirmed when the masker changed it, kept as audit evidence and committed so the receipt opens it offline. A rule can require one, and a reason is corrected by appending, never edited. |
| Approval binding | An approval binds what will run, not only the request: a static, smart, or constructed inventory as snapshotted at submission, the container image by the digest its registry serves, and for a Terraform or OpenTofu apply the saved plan the apply carries out, whether a person marked the apply for approval, a rule held it, or a drift reconcile built it. At claim the executor recomputes the digest of the spec it is about to run and refuses a run that no longer matches the one approved. A dynamic inventory source binds its definition, and the outcome records the hosts it reached (Threat model). |
| Approval steps | A workflow can stop partway at an approval step. The steps before it run, the workflow waits, and an admin approves or denies from the approvals queue, the run page, or the API. Approval runs the steps after it, and a denial, or the step's timeout passing, runs its deny path instead, the way AWX takes an approval node's failure path. An agent can see that its workflow waits and cannot decide it. The decision is bound to the state the approver was shown, the paused workflow survives a restart and is resumed by exactly one replica, and the request and the decision are chain entries the run's dossier and receipt disclose. |
| Reversibility | Every run is graded on whether it can be taken back: reversible, costly, or irreversible. It is a different question from risk, and the two come apart constantly: restarting a fleet is high risk and undoes itself, while deleting a backup set reads quietly and is permanent. An Ansible run is graded from what it actually runs: the playbook's plays, tasks, and blocks, and every role, role dependency, task include, and imported playbook they pull in, installed Galaxy roles and collections included, read from the project at the commit the run executes. A Terraform or OpenTofu apply is graded from what its plan says it destroys, and a finished run from what it actually did, so a run that changed nothing has nothing to undo. An approval policy can hold on the grade, which is the rule most people want and could not write: on Team, a change nobody can take back can need a second person to agree to it, through a reversibility floor and a distinct approver. A rule with a risk or reversibility floor sends a Terraform or OpenTofu apply to be planned first, since its grade is only known once the plan has said what it destroys, and then holds or refuses the apply the plan proposes on that grade. |
| Estate history | Every fact gather is kept rather than overwritten, so the estate can be reconstructed as it stood on any date since the install. GET /v1/estate?at= answers what was running then; GET /v1/estate/diff?from=&to= answers what moved since, host by host and fact by fact, with the value at each end. Both report how far back the records reach, so an empty answer is never mistaken for an empty estate. Depth and spacing are configurable. |
| Changes | Label runs with a change and read the whole arc as one thing: GET /v1/changes/{name} returns every run that served one intent, its span, everyone who acted, and an outcome derived from the runs rather than declared. A rollback followed by a fix-forward reads as one mixed change, which is what really happened and what a single run can never express. |
| Policy as code (Pro past one plain rule, Team past five or for advanced rules) | Point --policy-file at a YAML file and it becomes the source of truth for policies. Community holds one plain rule and Pro holds five. Deny rules, actor, account, and risk scoping, reversibility floors, distinct approvers, and Rego need Team, as does a sixth rule. A rule can hold a run for approval or deny it outright (effect: deny), can scope itself to agents or people (actor_kind), to one named principal (actor) or the account its token is bound to (account), or to a risk floor (min_risk), and can demand that the approver be someone other than the requester (require_distinct_approver), so an agent's authorization boundary is a reviewed diff with a commit behind it. With no rule written, every run an agent token asks for already waits for a person, a dry run included, an agent's Terraform or OpenTofu apply waits once before it plans and again carrying the saved plan, and a Community rule with effect: exempt lets named routine work through, naming the account an agent's token is bound to whenever it names the agent's actor (Agent runs are held by default). The same file can load Rego policies, so an existing Open Policy Agent or Conftest rule ports as it is, evaluated offline against a documented input and recorded in the evidence by the digest of its exact bundle (Approval policies). The API refuses policy writes with a 409 that says where to make the change, and a malformed file stops the server rather than silently gating nothing. |
| Dry-run scan | A rule that lets dry runs through first reads each one for the known ways a check or a plan acts for real. The gate reads the playbook and everything it pulls in for tasks that set check_mode: false and for pipe lookups, which run a command on the controller while a template renders, and the Terraform or OpenTofu configuration and every module it calls for external and aws_lambda_invocation data sources and for http data sources with a write method or a request body. It records what it found in run.dry_run_findings and holds a dry run that could act for real the way the real run would be held, as it does a dry run of a tool a plugin added, which nothing scans. The scan does not prove a preview harmless, since lookups, plugins, and providers run code too, so a rule that exempts dry runs trusts the code it lets through. An agent's dry run waits for a person whatever the scan finds, unless an exemption covers it (Dry runs). |
| Observability | A Prometheus metrics endpoint, webhook notifications when runs finish or are held for approval, and an audit trail of every mutation. |
| Tamper-evident audit | Every mutation is linked into a hash chain of SHA-256 digests. GET /v1/audit/verify flags the first altered or deleted entry, and a signed LoomSeal bundle proves the whole chain offline to a third party running an open-source verifier published separately. A change that cannot be recorded is refused rather than performed silently. |
| Anchored history | switchtender audit anchor has a public RFC 3161 timestamp authority sign the current chain head. The token travels in every bundle and is checked offline by anyone, so a chain that has lost its tail no longer reaches its anchor. |
| Audit receipts | Every mutation other than signing in or out returns an Audit-Receipt header naming where it landed in the chain. switchtender audit receipt redeems one, so a party who holds a receipt can prove their entry was not dropped. |
| Receipt contents | A run's receipt discloses its spec, its outcome, and each approval decision and correction beside the digests the chain committed. A LoomSeal verifier from 1.7.0 on checks every one of them as carried, so a receipt edited and signed again fails in this product's verifier and in LoomSeal's alike, and a member nothing commits is reported as unchecked rather than read as verified. |
| Evidence report (Team) | switchtender audit report renders the period's changes into a self-contained HTML compliance report a vendor-security or SOC 2 reviewer reads with no tooling. The chain behind it is proved separately by verifying a signed bundle, so the narrative and the proof are independent artifacts. |
| Launch receipts | A run created by a recorded request, whether an API launch or a webhook fire, records the seq:link of the chain entry that authorized it, so who asked is redeemable evidence rather than a field the server asserts. A creation entry the chain can no longer produce is reported. A run the scheduler starts carries one too, pointing at the entry its schedule's fire committed. A run created before launch receipts existed carries none, and its dossier says so plainly rather than implying one was lost. |
| Run dossier | switchtender audit run <id>, or the run page's Evidence button, emits one self-contained document tying the run's spec, risk grade, approval decisions, per-host outcomes, chain receipts, and covering anchors together, so an auditor's sample request is answered in one export. |
| Recorded holds | A run held for approval records which rule held it, by the name that rule carried at the hold, so the register answers "what stopped this change" even after the policy is renamed or deleted. A plan held for destroying too much records the threshold it crossed. |
| Scheduled evidence packs (Team) | --evidence-dir with --evidence-cadence writes a change register covering each elapsed period, named for the period to the second, so an archive sorts chronologically and a missing period is a missing file. Progress lives in the archive rather than in memory, so a restart resumes where the last pack ended and a period that failed to write is covered by the next attempt instead of being skipped. The evidence a review samples from exists before it is asked for. |
| Change register (Team) | switchtender audit report --from --to, or the audit page's Evidence pack button, renders every change in the period with its actor, chain-recorded decision, risk grade, and outcome. That is the change-management sample that SOC 2 CC8.1 and ISO/IEC 27001 A.8.32 reviews ask for. |
| Chain witness | switchtender witness watches the public beat feed from another machine, remembers what it saw in a signed checkpoint, and raises a finding on a missing beat, a rewritten beat, or a head regression, so an insider with database access cannot silently truncate history. |
| Run comparison | Every run page carries a Compare button answering "this worked yesterday, what changed": host-by-host verdicts (broke, recovered, still failing), task timing swings, and the duration delta against the previous run of the same template, exportable like every table. |
| SIEM forwarding | --forward-url and --forward-syslog stream every audit entry into the tools your security team already watches, as NDJSON over HTTP or RFC 5424 syslog. Every event carries its seq:link receipt, so any event sampled from the SIEM months later is redeemable against the live chain with switchtender audit receipt. Delivery is at least once behind a durable cursor: an outage delays events, it does not drop them. |
| Hosted witness | switchtender witness serve watches many servers from one process, records every finding durably, and serves countersigned attestations of what it holds over a read-only API. A relying party verifies one offline with switchtender witness verify-attestation against the witness key they pinned, so "our history is intact" becomes a statement a third party signs rather than a claim the operator makes. Running your own is free on every tier. A KordLoom-operated one is a Team add-on, and included on Enterprise. |
| Inventories | Stored inventories referenced by id, materialized on whichever executor runs the play. |
| Inventory engine | Static INI, YAML, and JSON inventories, and smart inventories over them, resolve inside the binary the way Ansible's own plugins read them, quirks included, so a server without Ansible still reads them. A conformance corpus holds that reading in CI to every ansible-core minor from 2.16 to 2.21, and every Ansible run whose inventory the native engine resolved, at least in part, is checked against the executor's own ansible-inventory before the play starts and refused on any difference (Inventories). |
| Dynamic sources | Inventory plugins and scripts refreshed into stored inventories, with cloud auth from an env credential. |
| Sourced inventories | An inventory's content can come from a command, Vault, or Google Secret Manager, resolved at launch, so the host list need not live in SwitchTender. |
| Credentials | Fifteen kinds: SSH keys and SSH passwords, vault passwords, become passwords and full become settings, network device logins, env bundles for cloud SDKs, API tokens and JWTs, registry logins, Kubernetes kubeconfigs, and typed AWS, Azure, GCP, VMware, and OpenStack cloud credentials, all encrypted at rest. Custom types add their own fields and inject them as environment variables, extra vars, and private files. Four federated kinds, for AWS, Google Cloud, Azure, and any OpenID Connect relying party, store nothing and hand each run a short-lived identity token instead, as workload identity federation describes. The key that signs those tokens rotates on a fixed two-phase schedule, or at once in an emergency (Signing keys). |
| Secret masking | Credential values are redacted from run logs, live streams, and events, so a tool that echoes a secret shows *** instead of the value. |
| High availability | Two servers on one PostgreSQL database share the schedule without double-firing. It has no license check of its own: creating a new PostgreSQL schema is Team, and opening one that already exists is never gated. A token can carry a lifetime (token new --ttl) on any tier. |
| Git triggers | A webhook URL launches a template on push. The project syncs fresh, so it deploys the commit just pushed. |
| Pull request review | A GitHub or GitLab pull request is planned at its head commit in the tool's no-change mode, and the result is posted back as one comment per template, updated on every push, with a commit status. The comment carries the plan summary, the destroy count the rules weigh, the decision the apply would get after merge, a link to the run, and the receipt. A plan a rule holds says it is waiting for approval and links to where it is approved. Each report goes out once across replicas, survives a restart, waits for the plan's outcome on the chain, and is retried when the forge fails. A person whose GitHub or GitLab account is linked through OAuth, by its numeric id, can comment /switchtender plan or /switchtender apply, which acts as their SwitchTender account with the same role, grants, and separation of duties as the queue. A bot, a comment GitHub marks as an app's, and a fork's pull request are refused. An apply approves exactly the plan id the comment names, or the head's only plan when it was reported before the comment was written, after reading the comment back from the forge. The decision's chain entry records the forge, the comment's and author's ids, the SHA-256 of the comment body, and the plan it approved, and the run's receipt discloses them. See pull request review. |
| Surveys | Templates declare typed launch prompts, text, multiline, secret, integer, boolean, or choice, each with bounds checked before launch: min and max on a number, length and a regular-expression pattern on text, and a help line on any field. A launch that breaks a bound is refused with the field it failed, and injected as extra vars. A secret answer is sealed like a credential, never stored on the run as text, masked in the log, and recorded on the chain by name only. |
| Saved workflows | A template can carry a pipeline graph instead of a single tool, so a multi-step workflow with parallel branches, per-step tools, and retries is one saved object that a launch, a schedule, or a webhook fires the same way, and its survey answers reach every step. |
| Ansible controls | A run or template pins tags and skip_tags to select or exclude tagged plays, forks for how many hosts run at once, verbosity for -v through -vvvv, and diff_mode to show the before and after of every changed file, no hand-built command needed. They carry through schedules, triggers, and retries. |
| Relaunch failures | One action re-runs only the hosts a finished run left failed or unreachable, targeted at exactly that set, so a partial outage is closed without repeating the hosts that already succeeded. |
| Provisioning callbacks | A template with allow_callbacks on lets a host in its inventory launch it against itself by posting a key, which is AWX's provisioning callback: matched to the caller by AWX's rules, limited to that host, gated by the same policies, and written to the audit chain before the run exists. A template imported from AWX also answers at its AWX callback address once the import knows its AWX id (Provisioning callbacks). |
| Worker queues (Team) | Target a run at a named queue. A worker serving that queue runs it and default workers leave it alone. Pin a queue on a run, a template, or an inventory, most specific wins, so queues work like AWX instance groups. A queue is grantable as queue:<name> and matchable in a policy, so choosing one is access-controlled on both sides rather than only on the worker's. |
| Dependency sync | A project's requirements.yml roles and collections install on each sync, so playbooks that need them just run. |
| Execution environments | A template, run, or project pins a container image and its runs execute inside it, with their own tool and system dependencies, for any of the seven tools. The most specific wins: run, then template, then project. Private registries pull with a stored credential. It is off until --allow-container-ee turns it on, and the host needs Docker or Podman. |
| Teams and grants | Group users into teams and grant read, use, or manage on a specific project, template, inventory, or credential, each level implying the ones below it. A read grant delegates view of one object without a global viewer role; a manage grant delegates editing and deleting without global admin. Grants layer on the global role and default open, so an ungranted object falls back to the caller's global role: separation between organizations is not enforced until --strict-grants is on. |
| Reach isolated networks (Team) | A worker dials out to the control node over the mesh relay with a token and needs no inbound port, so it runs jobs inside an air-gapped segment, a DMZ, or a customer network the control node cannot reach directly. Point switchtender worker --server at the control node and set the worker token. The control node announces what the worker does on the channels it is configured with, the runs it finishes and the applies its plans leave held, since a worker in such a segment holds no channels of its own and could not reach them if it did. A worker pool that registers a delivery key receives each claimed run's credentials and secret survey answers sealed to that key and bound to the claim, so credentialed runs execute there while the worker holds no database and no encryption key. |
| Backup and restore | One encrypted file holds the whole control plane: credentials, projects, templates, inventories, schedules, triggers, notification targets, and access. Sealed with the deployment key, so it is confidential and tamper-evident, and it restores into either SQLite or Postgres. Forge account links stay out of it, so each person links again after a restore (Backup and restore). |
| Retention | A sweeper drops old run events and deletes terminal runs past a configurable age, keeping the summaries the cross-run views need. |
| An SMTP notification on every finished or held run, or on failures only, alongside the webhooks. An attention alert is emailed under either setting. | |
| Slack | A formatted message posts to a Slack incoming webhook when a run finishes, with the run label, status, and elapsed time, and when a run is held for approval, with the rule holding it and who asked. |
| Mattermost and Rocket.Chat | The same message posts to a Mattermost or Rocket.Chat incoming webhook, which both accept the Slack-compatible payload. |
| Discord | A formatted message posts to a Discord incoming webhook when a run finishes or is held for approval, the same summary as Slack in Discord markdown. |
| Microsoft Teams | An Adaptive Card posts to a Teams incoming webhook when a run finishes, colored by outcome with the status, elapsed time, and any error, and when a run is held for approval, naming the rule and the requester. |
| ntfy | A notification publishes to the configured ntfy topic when a run finishes or is held for approval, at raised priority for a failure or a hold, with optional token auth for a protected topic. |
| PagerDuty | A failed or interrupted run triggers a PagerDuty incident through the Events API, deduplicated per run, so an on-call responder is paged. A succeeded run pages no one. |
| Grafana | A finished run posts an annotation to the Grafana annotations API, tagged with switchtender and the run status, so runs show as markers on dashboards. |
| Twilio SMS | A failed or interrupted run texts each configured phone number through the Twilio API. A succeeded run texts no one. |
| AI triage | An optional AI provider, local Ollama or a cloud API, explains a failed run on demand from its masked log and failed task events. Advisory only. It never runs anything, so every action still goes through the same policy and audit gates. |
| AI drafting | Describe a bash, python, powershell, or go step in the workflow editor and the provider drafts the script into the command field for you to review, edit, and save. The draft never executes on its own. |
| Drift reconcile (Team) | One click on a drifted target builds the fix, run for real and born held for approval: an Ansible host reruns its playbook limited to that host, and a Terraform directory applies the plan its drift check saved, the plan the approval binds. A machine proposes, a person releases, and the audit trail records both. |
| Fleet questions | Ask the overview a plain-language question and the provider answers from a bounded snapshot of run counts, recent runs, host health, and drift. Metadata only, rate limited, and it can start nothing. |
| Run proposals | Describe a run on the runs page and the provider turns it into a validated run the server builds. It is born held for approval, stamped with your exact words, and runs nothing until an admin reviews the generated command and releases it. The audit trail records the request and the decision. |
| Go SDK | One public package registers new execution tools, AI providers, secret engines, and notification channels, compiled into the binary. A registered tool validates, executes, masks, and audits like a built-in. See Extend in Go. |
| Drop-in plugins | The same extension builds as its own binary and loads from --plugins-dir on a stock release, no recompile. Plugins speak gRPC over a local socket with mutual TLS, supervised by the server, on the server and on workers. |
| Guided tours | A tour launcher in the top bar walks the product, the pitch, and the migration path step by step in the live UI, fully keyboard accessible. |
| Desktop mode | switchtender desktop serves on a stable loopback port, keeps its data in a private per-user directory, and opens the UI. Packaging recipes cover a macOS app bundle and a Windows installer. |
| Migration | switchtender import awx and import semaphore read an export and create the equivalent projects, inventories, templates, surveys, schedules, and credential shells, with a dry-run report first. import chef and import puppet bring a desired-state fleet across as an inventory, grouped by environment and role, since those systems hold no job definitions to carry and their cookbooks and manifests do not convert. An AWX workflow job template arrives as a workflow template and keeps its own schedules, unless its graph is one a pipeline cannot express, such as a node that runs other nodes on failure, which is reported and skipped whole rather than imported in part. switchtender import rundeck reads either a job export or a project archive, told apart by content, converting Quartz schedules and bringing secure options across as secret fields. Neither Rundeck artifact carries a node definition, so no inventory is imported from either and each node source is reported by kind and location instead. An archive brings one project when its source control configuration names a repository this can reach. switchtender import jenkins reads a Jenkins jobs directory or a zip of one, turning freestyle jobs into Bash templates, resolving Jenkins H notation into real cron times, bringing a password parameter across as a secret field, and refusing to import a poll trigger. switchtender import cron brings a crontab under governance, turning each cron line into an approved, recorded schedule. The per-source table is in what each source brings over. |
| Offboarding record | A schedule and a webhook trigger each record who created it. Neither stops firing when that account is deleted, because a schedule is organization infrastructure and a trigger's token belongs to the trigger, so halting production work the moment somebody leaves would be its own outage. The record is what an offboarding review needs to decide which to reassign or rotate. |
| Schedule overlap | A schedule waits for every run it fired to finish, including the apply a scheduled plan proposed and a person has not yet decided on. A five-minute schedule whose playbook takes eight minutes skips the tick rather than stacking a second copy of the same work on the same hosts, and stays on its cadence rather than firing the moment the slow run ends. Each skip is logged with the run it waited on. |
| Schedule timezones | A schedule reads its cron expression in an IANA timezone such as America/New_York, following that zone's daylight-saving shifts, so a nightly window stays put across the year instead of drifting with the server clock. A schedule created without a zone is given the server's zone by name, and one an earlier release stored without a zone runs in UTC (Upgrading). On the day a zone falls back the repeated hour fires once, not twice, and on the day it springs forward a slot inside the hour that never happens fires at the instant the clocks jumped, so a nightly job runs every night of the year. |
| Schedule recurrences | A schedule takes an RFC 5545 recurrence rule in place of a cron expression: a DTSTART with its zone, one or more RRULE lines, and EXRULE, RDATE, and EXDATE lines, so the last Friday of each quarter, every other Tuesday, or nightly except on a holiday is one schedule. Rules are expanded on the zone's wall clock and read through daylight-saving changes the way RFC 5545 defines, a rule bounded by COUNT or UNTIL fires its last occurrence and stops, and two servers on one database fire each occurrence once. An AWX import carries every schedule's rule across rather than skipping the ones cron cannot say. |
| Schedule failures | A fire that starts no run, because a credential has no secret, a policy denied it, or its template is gone, keeps its reason on the schedule. The Schedules page shows the reason in the Last run column beside a link to the last run that did start, and the next fire that starts a run clears it. |
| Starter templates | switchtender examples seeds a fresh install with a few templates that run with no project, inventory, or credential, so the first launch works on the spot instead of facing an empty list. |
Operator toolbelt
- Rerun any finished run with its exact stored spec, credentials included, from the run page. Split parents rerun as a new split. Every rerun is authorized like a fresh launch.
- Launch a template with overrides: host limit, a different stored inventory, selectable credential picks, extra vars merged over the template's, and a dry-run toggle. Overrides pass the same authorization as the template's own objects.
- The doctor at /ui/doctor verifies every registered reference: templates to inventories, projects, and credentials, schedules to templates, and every cron expression, plus credentials still waiting for a secret. It reports the server's ansible-core version against the releases the inventory engine is tested on. Each finding links to the page that fixes it.
- Schedules preview their next firings as you type the cron expression or the recurrence rule.
- Every list exports the filtered rows as CSV or JSON. Runs export events as NDJSON, the full log as text, and per-host results as JSON. Credentials are excluded from export on purpose.
- Per-template notification routing is editable in the template dialog, with failure-only delivery per target. All eleven channels take a per-template target. Webhook, Slack, Mattermost, Rocket.Chat, Discord, Teams, and ntfy targets name a URL. A PagerDuty target names its own routing key and a Grafana target its own instance and token, so each template can page or annotate its own team. A Twilio or email target names only a recipient and sends through the server-held account, so the account secret never lives in a template. Routing keys and tokens are masked on read the same as webhook URLs.
- Notification targets are defined once and attached to many objects, the way AWX attaches a notification template: a target attached to a template, a workflow, a schedule, a project, or an organization hears that object's runs when they start, succeed, fail, or are held for approval, and a schedule's fires when they are skipped because its inventory matched no hosts. A target's address and key are sealed at rest and never returned, and an AWX import brings its notification templates and their attachments across, an organization's own included.
- Each run's notifications reach a target in the run's event order. A failed one is retried, then kept as failed on the run, on the target, and in the doctor, and the next one still goes with a note saying an earlier one failed. The Notifications page shows each target's delivery status and what it still needs. Each delivery is queued in the database in the same write that records its event, so a server that stops leaves it for the next server to make rather than losing it.
- The overview charts runs per day, and every outcome tick in fleet health links to its run.
Run provenance and search
- Every run records what fired it: the API, a template, a schedule, a webhook trigger, a rerun, a drift fix, a proposal, or a pull request review, along with the authenticated actor and the object behind it. The runs list shows an Origin column that links back to the template, schedule, or earlier run.
- A rerun records the run it replayed, so lineage survives across repeated executions.
- Runs carry labels, arbitrary key values such as env=prod or ticket=OPS-123, set at launch and clickable in the list to filter by that pair.
- The runs search box accepts fielded terms alongside free text: status:failed, tool:bash, source:schedule, actor:alice, host:web01, worker:web-01, held_by:"prod freeze", and label:env=prod, all resolved by the server across the whole history rather than the loaded page. A value holding spaces is double-quoted.
- Playbook names open the file itself, read only, from the project's cached checkout.
Host facts
- A play that gathers facts records what each host is: distribution and version, kernel, architecture, vCPUs, memory, address, service manager, virtualization, and Python version.
- The facts appear on the host's page with when they were gathered and a link to the run that gathered them. A later gather replaces an earlier one, so the page always shows current truth.
- Nothing is gathered unless a play asks for it, so a fleet that runs with
gather_facts: falsestores nothing and the page says so.
Running a public demo
switchtender demoseeds a database with sample projects, templates, inventories, and real runs, then serves it with every change rejected, so it is safe to expose.--seed-onlyand--no-seedsplit seeding from serving, so a public instance can build its next database off to the side and swap it in without a gap in service.
