Kerrigan Test Manifest Guide
Kerrigan test runners use a YAML manifest to describe what should run on the device fleet. In GitHub Actions, the workflow usually points the runner bridge at a checked-in manifest:
jobs:
kerrigan:
runs-on: [self-hosted, kerrigan]
env:
KERRIGAN_MANIFEST: .kerrigan/ctest-job.yaml
steps:
- uses: actions/checkout@v4
- run: kerrigan-runner-bridge --timeout-sec 1800
Prefer static manifests for normal CI. Dynamic manifest generation is useful for ad hoc experiments, but checked-in YAML is easier to review, reproduce, and reuse across branches.
For label-based resource selection details, see
resource selection. This guide
describes the current manifest schema, including labels and tags.
For an exhaustive field-by-field schema, see the
Workflow spec reference.
File Shape
metadata:
name: core-ctest-smoke
labels:
suite: core
mode: ctest
source:
repository: sima-neat/core
artifacts:
- name: ctest-runtime
url: https://example.com/artifacts/ctest-runtime.tar.gz
sha256: "<sha256>"
extract: true
destination: .
setup:
per_devkit:
- id: prepare-runtime
commands:
- echo "preparing ${KERRIGAN_TARGET_ID}"
- ./install-runtime.sh
timeout_sec: 300
scheduling:
strategy: spread
min_devkits: 0
max_devkits: 4
max_parallel: 4
fail_fast: false
pools:
- sim-pool
capabilities:
board: modalix
os_type: elxr
firmware: 2.1.0
tasks:
- id: ctest-core
type: ctest
test_dir: ctest-runtime/build/tests
selectors:
- ctest_shard_a
- ctest_shard_b
- ctest_shard_c
timeout_sec: 120
Metadata
metadata.name gives the session a readable name. metadata.labels are for
classification and reporting; they do not select devices.
metadata:
name: apps-regression
labels:
suite: apps
priority: nightly
Source
source tells device agents how to materialize the task workspace.
Use source.artifacts for realistic DevKit testing where agents download a
prebuilt runtime or test package:
source:
artifacts:
- name: ctest-runtime
url: https://shanguangzai.github.io/kerrigan-dispatcher-test/artifacts/ctest-runtime.tar.gz
sha256: 8cb4cf03f3c7bd7052ed12067c36a9c98a7ed6587ecba5f1db2952bf4aeccdca
extract: true
destination: .
Fields:
name: local artifact name used in logs and temporary files.url: downloadable URL.file://is supported by the simulation stack.sha256: optional but recommended. Agents verify it before execution.extract: when true, extract the tarball intodestination.destination: workspace-relative destination. Absolute paths and..are rejected.
Use source.bundle for small repository files, such as Python test fixtures:
source:
bundle:
include:
- tests/**/*.py
exclude:
- tests/experimental/**
Bundles are intended for small files. Large runtimes, binaries, SDKs, and CTest trees should be hosted as artifacts.
Use source.vulcan_artifacts when the runtime is published through Neat CICD
Infrastructure (Vulcan) and should be installed uniformly on every selected
devkit:
source:
vulcan_artifacts:
- env: stg
name: core
branch: develop
tag: latest
type: minimal
install_dir: /tmp/kerrigan-vulcan-core-develop
skip_devkit_sync: true
timeout_sec: 300
Fields:
env: Neat CICD Infrastructure environment, such asdev,stg, orprd.name: artifact name, such ascore.branch: artifact branch, such asmain.tag: artifact tag, such aslatestor a git hash.type: optional Neat CICD Infrastructure install type passed assima-cli neat install --type. Useminimalfor smoke tests when the full artifact has optional resource prompts.install_dir: optional install directory passed as--install-dir.base_url: optional explicit artifact base URL passed as--base-url.force: optional boolean that passes--force.skip_devkit_sync: optional boolean that setsNEAT_INSTALLER_SKIP_DEVKIT_SYNC=ONfor this install. This is useful in SDK or simulation containers where a paired physical DevKit may be visible but the test should only validate artifact retrieval/install in the agent container.timeout_sec: optional install timeout in seconds. The default is900.
The device agent installs each Neat CICD Infrastructure artifact before running setup or test commands:
sima-cli neat install --type minimal \
--install-dir /tmp/kerrigan-vulcan-core-develop core@develop:latest
This is useful for real DevKit workflows where the test package or runtime
should be installed through the same path a developer would use manually.
Kerrigan caches the install per devkit agent process, so repeated tasks on the
same devkit do not reinstall the same env/name/branch/tag tuple.
Setup
setup.per_devkit runs once on every selected devkit before normal tasks start.
Use it to prepare runtime state, mount paths, start background services, or
validate device readiness.
setup:
per_devkit:
- id: prepare-runtime
commands:
- export PATH="/opt/runtime/bin:$PATH"
- ./scripts/start-helper.sh &
- ./scripts/check-device-ready.sh
timeout_sec: 300
output_idle_timeout_sec: 60
The command list is executed by the agent as one shell command joined with
&&. Shell syntax such as &&, ;, and & is available. If setup fails on any
selected devkit, Kerrigan fails the session and skips remaining test tasks.
Use output_idle_timeout_sec for setup steps that should continuously report
progress, such as package downloads or model retrieval. This catches accidental
interactive prompts, stalled network downloads, or tools waiting for login.
Device-agent commands run with stdin closed and CI=true, so workflows should
not depend on interactive input.
Teardown
teardown.per_devkit runs once on every selected devkit after normal tasks have
reached a terminal state. Use it for cleanup that must happen on every device,
such as removing temporary MPK extraction directories, stopping helper services,
or collecting final disk usage. Unlike a normal task, teardown is not spread to
one available devkit; Kerrigan fans it out across the devkits selected for the
session.
teardown:
per_devkit:
- id: cleanup-runtime
shell: |
rm -rf /tmp/sima-mpk-kerrigan-core-ci*
find /tmp -maxdepth 1 -name 'sima_gst_registry_*.bin' -delete
timeout_sec: 120
Teardown still runs after test failures, so the session can preserve failure evidence in the task summary while returning devices to a clean state. A teardown failure is reported as its own task failure in the final session summary.
Scheduling
scheduling controls admission, fanout, and device matching.
scheduling:
strategy: spread
min_devkits: 2
max_devkits: 4
max_parallel: 4
fail_fast: false
pools:
- sim-pool
capabilities:
board: modalix
os_type: elxr
labels:
kerrigan.pool_id: sdk-2-2-som
sdk: 2.2.0
tags:
- 2.2.0-som-devkits
Fields:
strategy:spread,pack, orserial.min_devkits: minimum eligible devkits required before dispatch starts.max_devkits: maximum distinct devkits the session may use.max_parallel: maximum concurrent task assignments.fail_fast: when true, queued work is cancelled after a test failure.pools: eligible device pools.capabilities: device capability requirements.labels: exact-match resource labels. Kerrigan provides implicit labels such askerrigan.pool_id,kerrigan.target_id, andkerrigan.board.tags: shorthand selectors equivalent totag.<name>: true.
min_devkits: 0 means "do not require a minimum before starting." Kerrigan will
run with whatever eligible capacity is available, capped by max_devkits. It
still cannot run tasks until at least one eligible devkit is available.
Examples:
# Fallback-friendly: use up to four, but start with one if that is all we have.
scheduling:
min_devkits: 0
max_devkits: 4
# Strict fanout: wait until at least two eligible devkits are available.
scheduling:
min_devkits: 2
max_devkits: 4
# Use at most four devkits, but only run two tasks at a time.
scheduling:
max_devkits: 4
max_parallel: 2
Scheduling Strategy
strategy controls how Kerrigan chooses among eligible devkits when assigning
ready tasks. It does not change which tasks exist; selectors and task
definitions control that. It also does not override min_devkits,
max_devkits, or max_parallel.
spread
spread balances work across the selected devkits. It prefers the eligible
devkit with the fewest assignments so far.
Use spread for normal parallel test execution:
scheduling:
strategy: spread
min_devkits: 0
max_devkits: 4
max_parallel: 4
With six ready shards and four available devkits, the first wave uses four devkits. As devkits finish, remaining shards are assigned to the next available devkit while keeping assignment counts as balanced as possible.
Use this for:
- CTest shards.
- Pytest shards.
- Independent smoke/regression tests.
- Any workload where shorter wall-clock time is the goal.
pack
pack prefers reusing already-selected devkits before expanding to more
devkits. It keeps the session footprint smaller when full fanout is not needed.
scheduling:
strategy: pack
min_devkits: 0
max_devkits: 4
max_parallel: 2
With two concurrent slots, pack tries to keep work concentrated on the same
small set of devkits instead of spreading assignments across all four. This can
reduce setup cost, cache misses, device churn, or contention with other users.
Use this for:
- Tests with expensive per-device setup.
- Workloads that benefit from warm caches or reused state.
- Low-priority jobs where minimizing fleet footprint matters more than perfect distribution.
pack still respects max_parallel. If max_parallel: 2, it will not run more
than two tasks at the same time.
serial
serial runs at most one task at a time. It is equivalent to a conservative
single-lane schedule even if multiple devkits are available.
scheduling:
strategy: serial
max_devkits: 1
max_parallel: 1
Use this for:
- Tests that mutate shared lab state.
- Firmware update or recovery flows.
- Debug workflows where ordered logs matter.
- Any suite that is not safe to parallelize yet.
For clarity, pair serial with max_parallel: 1. If you need strict use of a
specific kind of device, express that with pools and capabilities.
Tasks
Selectors define the partial test scope assigned to one devkit. Good selectors are the main difference between "Kerrigan can distribute this suite" and "one devkit has to run everything."
Guidelines:
- Use selectors that are independently runnable.
- Keep selector runtimes reasonably balanced.
- Prefer many medium-sized selectors over one giant selector.
- Avoid selectors that depend on execution order unless dependencies are explicitly modeled.
- Make selectors stable names, not line numbers or temporary generated IDs.
CTest
Use type: ctest when the artifact contains a CTest tree. selectors are
expanded into independently schedulable tasks:
tasks:
- id: ctest-core
type: ctest
test_dir: ctest-runtime/build/tests
selectors:
- ctest_shard_a
- ctest_shard_b
env:
KERRIGAN_CTEST_SHARD_DURATION_SEC: "10"
timeout_sec: 120
Kerrigan runs each selector as:
ctest --test-dir ctest-runtime/build/tests --output-on-failure -R '^ctest_shard_a$'
Each selector is treated as an exact CTest test name. Kerrigan wraps it as
^name$ for ctest -R, so ctest_shard_a matches only ctest_shard_a, not
ctest_shard_a_extra.
You can inspect the names available in a CTest package with:
ctest --test-dir ctest-runtime/build/tests -N
If that prints:
Test #1: ctest_shard_a
Test #2: ctest_shard_b
Test #3: ctest_shard_c
then the manifest selectors should be:
tasks:
- id: ctest-core
type: ctest
test_dir: ctest-runtime/build/tests
selectors:
- ctest_shard_a
- ctest_shard_b
- ctest_shard_c
For real CTest suites, structure the package so partial execution is natural. For example, define separate CTest tests by shard, suite, model, board mode, or test binary:
add_test(NAME core_graph_smoke COMMAND core_tests --suite graph_smoke)
add_test(NAME core_tensor_smoke COMMAND core_tests --suite tensor_smoke)
add_test(NAME core_runtime_smoke COMMAND core_tests --suite runtime_smoke)
Then select those names:
selectors:
- core_graph_smoke
- core_tensor_smoke
- core_runtime_smoke
A single monolithic CTest target cannot be evenly distributed across devkits. If
ctest -N shows only one long test, Kerrigan can only assign that one test to
one devkit. Split the CTest package first, then list the split names in
selectors.
Use selector instead of selectors when there is only one CTest name:
tasks:
- id: ctest-one-suite
type: ctest
test_dir: ctest-runtime/build/tests
selector: core_graph_smoke
Pytest
Use type: pytest for bundled Python tests:
tasks:
- id: smoke-a
type: pytest
selector: tests/test_slow_suite.py::test_manual_shard_a
timeout_sec: 60
The runner bridge normalizes this to a command similar to:
pytest -q -s tests/test_slow_suite.py::test_manual_shard_a
Pytest selectors are normal pytest node IDs. Common forms are:
# Entire file
selector: tests/test_runtime.py
# One test function
selector: tests/test_runtime.py::test_boot
# One class
selector: tests/test_runtime.py::TestRuntime
# One test method
selector: tests/test_runtime.py::TestRuntime::test_boot
For sharding, create one task per selector:
tasks:
- id: pytest-runtime-a
type: pytest
selector: tests/test_runtime.py::test_runtime_shard_a
timeout_sec: 120
- id: pytest-runtime-b
type: pytest
selector: tests/test_runtime.py::test_runtime_shard_b
timeout_sec: 120
Use pytest selectors when the tests are small enough to bundle from the repo. For larger test assets or compiled binaries, prefer artifact-backed CTest or an explicit command task that downloads and runs a package.
Command
Use type: command for explicit commands:
tasks:
- id: custom-smoke
type: command
command:
argv:
- ./run-smoke.sh
- --target
- "${KERRIGAN_TARGET_ID}"
env:
LOG_LEVEL: debug
cwd: ctest-runtime
timeout_sec: 300
The device agent injects KERRIGAN_TARGET_ID for the assigned devkit.
Requirements Per Task
Tasks can override or refine global scheduling requirements:
tasks:
- id: firmware-upgrade
type: command
command:
argv: ["./upgrade-fw.sh"]
requirements:
pools:
- lab-a
capabilities:
board: modalix
role: controller
labels:
fixture: smoke
tags:
- modalix
Use this when only some tasks need special hardware. Keep common requirements in
scheduling.capabilities or scheduling.labels.
Retries
Retries can distinguish test failures from infrastructure failures:
tasks:
- id: flaky-link-test
type: command
command:
argv: ["./run-link-test.sh"]
retries:
test_failure: 0
infra_failure: 1
Use infrastructure retries for device disconnects, artifact fetch failures, or other non-test failures. Avoid retrying deterministic test failures by default.
GitHub Log Expectations
The runner bridge prints session progress into GitHub Actions logs. Artifact retrieval is reported by device agents through the control plane:
runner-bridge: progress #... ctest-core-ctest-shard-a target=sim-devkit-01: downloading artifact ctest-runtime from https://...
runner-bridge: progress #... ctest-core-ctest-shard-a target=sim-devkit-01: downloaded artifact ctest-runtime (... bytes)
runner-bridge: progress #... ctest-core-ctest-shard-a target=sim-devkit-01: verified artifact ctest-runtime sha256 ...
runner-bridge: progress #... ctest-core-ctest-shard-a target=sim-devkit-01: extracted artifact ctest-runtime to .
Task output includes timestamp and devkit:
runner-bridge: [2026-05-27T10:11:12.345-07:00] [sim-devkit-01] | Test project /tmp/...
Recommendations
- Put stable manifests under
.kerrigan/. - Prefer artifact-backed CTest packages for realistic DevKit testing.
- Keep test tasks independently selectable so Kerrigan can shard them.
- Use
min_devkits: 0for smoke tests that should run with whatever is free. - Use
min_devkits >= 2when the test result is only meaningful with fanout. - Always specify
max_devkitsto bound fleet usage. - Always set task timeouts.
- Include
sha256for downloadable artifacts. - Keep setup idempotent; it may run once per selected devkit.