Skip to content

Multiple Workflows

llms.txt lists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Read https://intentius.io/chant/lexicons/github/multi-workflow/.
Move the jobs of the workflows I name into one chant source with `Workflow.jobs`, so each workflow writes its own file under `.github/workflows/`.
Run `npx chant build src --lexicon github -o .github/workflows/pipeline.yml` and `npx chant lint src`, diff the generated files against the old ones, and open a pull request with each difference listed.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.

A single Chant source directory can produce multiple .github/workflows/*.yml files. Export multiple Workflow entities and scope jobs to each one using Workflow.jobs.

Pass jobs directly in the Workflow constructor using the jobs prop:

import { Job, Step, Workflow } from "@intentius/chant-lexicon-github";
export const nightly = new Workflow({
name: "Nightly Build",
on: { schedule: [{ cron: "0 2 * * *" }] },
permissions: { "id-token": "write", contents: "read" },
jobs: {
build: new Job({
"runs-on": "ubuntu-latest",
"timeout-minutes": 30,
steps: [
new Step({ name: "Checkout", uses: "actions/checkout@v7" }),
new Step({ name: "Build", run: "npm run build" }),
],
}),
},
});
export const weekly = new Workflow({
name: "Weekly Report",
on: { schedule: [{ cron: "0 9 * * 1" }] },
jobs: {
report: new Job({
"runs-on": "ubuntu-latest",
steps: [
new Step({ name: "Generate report", run: "./scripts/report.sh" }),
],
}),
},
});

The primary output file is named by the -o flag. Additional workflow files are named by converting the export variable name to kebab-case and appending .yml:

Export nameOutput file
-o pipeline.yml (primary)pipeline.yml
export const dbSnapshotdb-snapshot.yml
export const weeklyweekly.yml
export const ciLintci-lint.yml

When building, pass the primary output path; all other workflows are written alongside it:

Terminal window
chant build src --lexicon github -o .github/workflows/pipeline.yml

This produces:

.github/workflows/
pipeline.yml ← primary (first workflow alphabetically)
db-snapshot.yml ← from export const dbSnapshot
weekly.yml ← from export const weekly

Single workflow — one Workflow export, standalone Job exports attach to it automatically:

// Single workflow: standalone Job exports attach here
export const workflow = new Workflow({ name: "CI", on: { push: null } });
export const build = new Job({ "runs-on": "ubuntu-latest", ... });
export const test = new Job({ "runs-on": "ubuntu-latest", ... });

Multiple workflows — each Workflow owns its jobs via Workflow.jobs:

// Multi workflow: each Workflow owns its jobs
export const ci = new Workflow({
name: "CI",
on: { push: { branches: ["main"] } },
jobs: {
build: new Job({ "runs-on": "ubuntu-latest", ... }),
test: new Job({ "runs-on": "ubuntu-latest", ... }),
},
});
export const deploy = new Workflow({
name: "Deploy",
on: { workflowDispatch: null },
jobs: {
release: new Job({ "runs-on": "ubuntu-latest", ... }),
},
});

Standalone Job exports without Workflow.jobs still work when there is only one Workflow. When multiple workflows are present and none define Workflow.jobs, standalone jobs fall back to the first workflow, as they did before multi-workflow support, so composites such as DockerBuild and DeployEnvironment work unchanged.

A common use case is ops workflows that need manual dispatch:

import { Job, Step, Workflow } from "@intentius/chant-lexicon-github";
export const dbBackup = new Workflow({
name: "DB Backup",
on: {
workflowDispatch: {
inputs: {
label: { description: "Backup label", required: false, default: "manual", type: "string" },
},
},
schedule: [{ cron: "0 3 * * 0" }],
},
permissions: { "id-token": "write", contents: "read" },
jobs: {
backup: new Job({
name: "Create backup",
"runs-on": "ubuntu-latest",
"timeout-minutes": 30,
environment: { name: "production" },
steps: [
new Step({
name: "Configure AWS credentials",
uses: "aws-actions/configure-aws-credentials@v4",
with: {
"role-to-assume": "${{ vars.AWS_DEPLOY_ROLE_ARN }}",
"aws-region": "${{ vars.AWS_REGION }}",
},
}),
new Step({ name: "Create backup", run: `./scripts/backup.sh` }),
],
}),
},
});
export const dbRestore = new Workflow({
name: "DB Restore",
on: {
workflowDispatch: {
inputs: {
snapshot: { description: "Snapshot identifier", required: true, type: "string" },
confirm: { description: "Type CONFIRMED to proceed", required: true, type: "string" },
},
},
},
permissions: { "id-token": "write", contents: "read" },
jobs: {
restore: new Job({
name: "Restore from snapshot",
"runs-on": "ubuntu-latest",
"timeout-minutes": 60,
environment: { name: "production" },
steps: [
new Step({
name: "Verify confirmation",
run: `if [ "${{ github.event.inputs.confirm }}" != "CONFIRMED" ]; then exit 1; fi`,
}),
new Step({
name: "Configure AWS credentials",
uses: "aws-actions/configure-aws-credentials@v4",
with: {
"role-to-assume": "${{ vars.AWS_DEPLOY_ROLE_ARN }}",
"aws-region": "${{ vars.AWS_REGION }}",
},
}),
new Step({ name: "Restore", run: `./scripts/restore.sh "${{ github.event.inputs.snapshot }}"` }),
],
}),
},
});

Both workflows are declared in one file and produce separate db-backup.yml and db-restore.yml output files.

chant