# Architecture (/docs/architecture)



Actio is a **compiler**, not a runtime. You author a `.actio.yml` source — a strict
[superset](/docs/syntax) of GitHub Actions workflow syntax — and `actio build`
expands it into the verbose standard workflow YAML you'd otherwise hand-write. Two
phases, both at **build time**:

1. **Macro expansion** — the keywords Actio adds (the whole
   [syntax reference](/docs/syntax)) are transformed into plain workflow YAML.
2. **Compile-time interpolation** — `{{ ... }}` tokens are resolved and baked into
   the output; `${{ ... }}` is left verbatim for the runner to evaluate. See
   [interpolation tokens](/docs/syntax#interpolation-tokens).

Nothing Actio adds survives into the generated file: **zero runtime, zero lock-in**.
A macro-free `.actio.yml` is already a valid workflow, and you can walk away at any
time by keeping the generated `.yml`. The rest of this page is the pipeline that does
the compiling.

## How it works [#how-it-works]

```text
actio build [globs]
  discover .actio.yml files
  per file:
    1. parse with eemeli `yaml` (comment- and position-preserving)
    2. run transform passes in dependency order
    3. resolve final compile-time text boundaries (`{{ ... }}` interpolation)
    4. serialize back to standard YAML (block scalars preserved)
    5. prepend a "generated by Actio" header
    6. validate the OUTPUT with @actions/workflow-parser (official GHA schema)
    7. write .github/workflows/<name>.yml + <name>.yml.map  (or --stdout / --check)
```

* **Front-end:** [eemeli `yaml`](https://github.com/eemeli/yaml) v2 — permissive,
  round-trippable, preserves `run: |` block scalars. (The official parser validates
  during parse and would reject our macro keywords.)
* **Output validation:**
  [`@actions/workflow-parser`](https://github.com/actions/languageservices/tree/main/workflow-parser)
  — the same parser behind the
  [GitHub Actions language server](https://github.com/actions/languageservices).
  Every generated workflow is fed back through it, so Actio only ever emits a legal
  workflow.

Each transform is a **pass** — a `{ name, runsAfter?, apply }` descriptor. The
pipeline order is derived by topologically sorting each pass's `runsAfter`, not
hand-maintained, so adding a feature is: drop in a new file, declare what it runs
after, register it. `PassRegistry` (exported from `actio-core`) lets external code
add or remove passes without editing core.

Built-in pass order today:
`params → call-templates → job-defaults → for-each → when-compile → fragments → share → retry → fallback → dynamic-matrix → lifecycle → if-changed → injection-hoist`.

After every registered pass has run, Actio performs one final compile-time text
boundary phase. This is where leftover `{{ params.* }}` and
`{{ toJSON(params.*) }}` tokens in ordinary scalar text are turned into emitted
literals. `transpile()`, `runPasses()`, and `PassRegistry.run()` all execute this
complete public pipeline. `applyPasses()` is the lower-level escape hatch for tests
or tooling that intentionally needs the raw pass-only stage; it can leave
`{{ ... }}` tokens unresolved.

## Typed IR and provenance [#typed-ir-and-provenance]

Passes operate on a small **typed IR** that wraps `ctx.data` rather than replacing
it — the emitted YAML stays byte-for-byte identical. `workflow(ctx)`, `visitJobs`,
`visitSteps`, and the in-place `transformSteps` fan-out helper give passes (and
third-party plugins) typed `Job`/`Step` views instead of raw
`Record<string, any>`.

Every node can carry an **origin** — the source path/range it came from — held in a
`WeakMap` side-table (`ctx.origins`) keyed by object identity, so it survives index
shifts and never serializes. The visitor records origins on first sight; `cloneNode`
and `deriveNode` propagate them so generated nodes (retry attempts and sleep steps,
the `dynamic-matrix` setup job) map back to their macro source. This is the hook
[source maps](/docs/source-maps) build on. Look up an origin with
`originOf(ctx, node)`.

## Packages [#packages]

| Package                                                                      | Description                                            |
| ---------------------------------------------------------------------------- | ------------------------------------------------------ |
| [`actio-core`](https://github.com/austenstone/actio/tree/main/packages/core) | The engine: parse, passes, emit, validate, diagnostics |
| [`actio-cli`](https://github.com/austenstone/actio/tree/main/packages/cli)   | The `actio` binary (wraps core)                        |

`actio-core` exposes the programmatic TypeScript API. Prefer `transpile()` when you
want generated workflow YAML, and prefer `runPasses()` or `PassRegistry.run()` when
you already have a parsed context and want the complete transformed workflow model.


## Sitemap

Browse the full documentation: [Markdown sitemap](https://austenstone.github.io/actio/sitemap.md) · [XML sitemap](https://austenstone.github.io/actio/sitemap.xml)