Skip to content

Migrating from GitHub Actions

KiCI workflows are real, typed TypeScript in .kici/workflows/*.ts instead of YAML in .github/workflows/*.yml. The compiler validates them ahead of time, and at run time the agent clones your repository and executes them on your own infrastructure. This guide maps the GitHub Actions concepts you already know to their KiCI equivalents, translates one realistic workflow side-by-side, and lists honestly what has no equivalent yet.

Follow getting started for the full setup. The mapping below assumes you have @kici-dev/sdk installed.

GitHub ActionsKiCINotes
Workflow file .github/workflows/ci.ymlTypeScript module .kici/workflows/ci.ts that default-exports workflow('ci', {...})Workflows are modules built from workflow(), job(), and step() factories.
on: pull_request:on: pr({ target: 'main' })pr() is a trigger factory; on takes one trigger or an array of triggers.
on: push: branches: [main]on: push({ branches: ['main'] })push() matches by branch pattern.
on: schedule: - cron:on: schedule({ cron: '0 2 * * *', timezone: 'UTC' })cron is required; timezone defaults to UTC. In a cluster only the leader evaluates schedules.
on: workflow_dispatch: inputs:on: dispatch({ inputs: defineDispatchInputs({...}) }), read typed via inputs.from(ctx)Dispatch inputs are declared with Zod schemas and read back fully typed.
jobs: map keyed by namejobs: [job('lint', {...}), job('test', {...})]Jobs are an array of job() results.
needs: [build] (string references)needs: [buildJob] (object references)You reference the job() value directly; the compiler validates the dependency graph for cycles and missing references.
runs-on: <hosted runner>runsOn: 'kici:os:linux' or a custom scaler labelEvery agent self-reports labels such as kici:os:linux, kici:arch:x64, and kici:host:<hostname>. A GitHub-hosted runner label matches no KiCI agent.
steps: - run: npm teststeps: [step('test', async ({ $ }) => { await $\npm test` })]`The $ in a step body is a zx shell; a single-step job can use the run: function shorthand instead of steps:.
steps: - uses: actions/checkout@v4(nothing)The agent clones the repository automatically before steps run, so there is no explicit checkout step.
steps: - uses: actions/setup-node@v4Provision the toolchain in a step or from the agent imageKiCI has no uses:-style setup actions; the toolchain comes from the agent environment or an explicit step.
${{ secrets.NPM_TOKEN }}await ctx.secrets.get('NPM_TOKEN'), or ctx.secrets.expose('NPM_TOKEN') to place it in the environmentSecrets are never auto-injected into process.env; access is always explicit.
environment: production (+ protection rules)context: 'production' (or contexts: ['staging', 'prod'])A context carries variables, bound secrets, and protection rules (branch restrictions, required reviewers, wait timers, concurrency limits, minimum trust).
env: (job or step level)env: { KEY: 'val' } on a jobJob-level env accepts a static object or a (event) => ({...}) function for dynamic values.
strategy: matrix: node: [18, 20, 22]matrix: ['18', '20', '22'] or { node: [...], os: [...] }A single-dimension array exposes matrix.value; an object expands all combinations and exposes each dimension by name. A dynamic function form is also supported.
if: github.ref == 'refs/heads/main'Native TypeScript conditionals plus rule() / skip() and dynamic valuesConditions are real TypeScript branching; dynamic values are pure functions of the normalized event.
concurrency: group: ...concurrencyGroup: 'production-api' (static) or a dynamic functionSet at the job level; a workflow-level concurrency group also exists.
jobs.<id>.outputsStructured job and step outputs consumed downstream via needs; ctx.setSecretOutput(key, value) for encrypted outputsOutputs pass values between jobs; secret outputs are encrypted.
- uses: actions/cache@v4cache field on a job or step (declarative) or ctx.cache.restore / ctx.cache.save (imperative)A keyed cache, immutable once written, org- and ref-scoped, backed by the orchestrator’s object storage.
Reusable workflows / composite actions / marketplace uses:@kici-dev/action-* building blocks or any npm package you importReusable logic is imported as functions, not referenced by uses:.

KiCI never copies secrets into process.env for you. A step reads a value with ctx.secrets.get('KEY') or injects it into the environment with ctx.secrets.expose('KEY'), and can mount a secret as a file with ctx.secrets.mountFile(...). Every access is tracked. See secrets.

A GitHub environment maps to a KiCI context bound at the job level with context: (or contexts: for several). A context carries variables, bound secrets, and protection rules — branch restrictions, required reviewers, wait timers, concurrency limits, and a minimum-trust gate. See contexts.

There is no expression mini-language. Conditions are ordinary TypeScript, and values that depend on the event are pure functions of the normalized event object. See dynamic values.

A single-dimension matrix is an array (matrix: ['18', '20', '22']) and exposes the current value as matrix.value in the step context. A multi-dimension matrix is an object and exposes each dimension by name. See conditionals and matrix patterns.

Declare a cache on a job or step, or drive it imperatively with ctx.cache.restore(spec) and ctx.cache.save(spec). Cache entries are keyed and immutable once written. See caching.

Here is a pull-request CI workflow that runs tests across a Node version matrix and uploads coverage using a secret.

The GitHub Actions version:

name: ci
on:
pull_request:
branches: [main]
jobs:
test:
# kici-lint-allow-github-runner: GitHub-hosted runner shown for contrast
runs-on: ubuntu-latest
strategy:
matrix:
node: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
- run: npx codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

The KiCI translation:

import { workflow, job, step, pr } from '@kici-dev/sdk';
const test = job('test', {
runsOn: 'kici:os:linux',
matrix: ['18', '20', '22'],
context: 'ci',
steps: [
step('install', async ({ $ }) => {
// The agent already cloned the repo — no checkout step needed.
await $`npm ci`;
}),
step('test', async ({ $, matrix }) => {
// matrix.value is the Node version for this cell ('18' | '20' | '22').
// Select it however your agent provisions toolchains, e.g. a version manager.
await $`nvm use ${matrix!.value}`;
await $`npm test`;
}),
step('coverage', async (ctx) => {
await ctx.secrets.expose('CODECOV_TOKEN');
await ctx.$`npx codecov`;
}),
],
});
export default workflow('ci', {
on: pr({ target: 'main' }),
jobs: [test],
});

What changed and why:

  • on: pull_requeston: pr({ target: 'main' }).
  • runs-on: ubuntu-latestrunsOn: 'kici:os:linux', an auto-label every Linux agent reports; a GitHub hosted-runner label would match no agent. See runsOn forms.
  • actions/checkout → removed; the agent clones the repository before steps run.
  • actions/setup-node with matrix.nodematrix: ['18', '20', '22'], with the current value available as matrix.value in the step context. KiCI has no built-in setup-node, so toolchain selection is a step or agent-image concern. See conditionals and matrix patterns.
  • ${{ secrets.CODECOV_TOKEN }}ctx.secrets.expose('CODECOV_TOKEN'), explicit and never auto-injected. See secrets.
  • environmentcontext: 'ci'. See contexts.

The nvm use line is illustrative — KiCI does not install a Node version for you; use whatever your agent image or step provides.

File artifacts between jobs. KiCI has no first-class store for uploading a build directory as a named artifact and downloading it in a later job or from the run UI. It does have structured job and step outputs (and secret outputs) for passing values, and a keyed cache for reusing files across runs. For build outputs you need to hand between jobs, use the cache or an external object store.

A community action marketplace. GitHub Actions has thousands of third-party marketplace actions addressable by uses: owner/repo@ref. KiCI’s reusable blocks are the published @kici-dev/action-* packages plus any npm package you import — there is no marketplace of community-contributed actions.

uses:-style step references. KiCI steps are TypeScript functions, so you call reusable logic as imported library functions rather than referencing a composite or container action. This is a model shift rather than a missing feature, but a drop-in uses: translation does not exist.

Provider breadth. KiCI is GitHub-first. Other git hosts are reachable through the universal-git and local-file providers, but the richest event coverage is for GitHub. See the GitHub provider.