On this page
On this page
Get started
Adaptive CI experiments
Adaptive CI experiments
Adaptive CI needs file-level execution evidence before it can safely skip tests. The observation exporter reads existing Vitest resource-reporter logs. It does not execute tests, change CI routing, or provide evidence for a required check.
Export observations
For a run that already uses scripts/lib/vitest-resource-reporter.mts, capture
its stdout and supply a JSON context file with these fields:
repository: GitHubowner/repository.revisionandtree: the run's full Git commit and tree IDs.configDigest,lockDigest, andenvironmentDigest: SHA-256 digests of the effective test configuration, dependency lock inputs, and execution environment. Hash canonical inputs, including runtime, operating system, relevant environment settings, and runner configuration. Do not include raw credentials.
--dependencies is optional. When supplied, it must be the native Vitest
experimental.recordDependencies JSON from the same run and configuration.
The exporter cannot attest that a caller supplied the correct context or cache.
Retain the source run ID and provenance with the exported artifact. Missing or
ambiguous dependency entries stay null; they do not mean an empty impact set.
The reporter records the Vitest root so native paths (including UI-local paths)
can be rebased onto repository-relative identities. Global setup dependencies
apply to every file. A cache shared across incompatible runtime or pool
identities is treated as missing.
The export contains repository-relative source identities, counts, outcomes, durations, and dependencies. It does not copy test titles, errors, or log text.
Observation pilot
Ordinary canonical CI collects the first explicit unit-fast Vitest invocation
for each runtime in a job on schedules and on PR runs whose run ID ends in 0.
Frozen targets and other events retain their existing execution. Runtime
partitioning finishes before the reporter and dependency-recording options are
added. Native Bun tests retain their existing command and are not represented by
Vitest observations. The pilot never skips a test or changes a required check.
Each observed invocation uses a fresh Vite cache directory. This prevents native
dependency records from leaking between configurations or runtimes, but also
changes optimizer and transform-cache identity. Compare complete paired job
times, including this cold-cache cost and artifact upload. capturePreparationMs
excludes final serialization and file writing as well as native recording and
cache misses; it is not the complete collection overhead.
The ci-test-observations-* artifacts retain sanitized exports for seven days.
Each file is bounded to 8 MiB, with at most one invocation per Vitest runtime per
job. Source metadata includes the workflow run, attempt, job, timestamps and
actual checkout commit/tree. Configuration identity hashes native config
dependencies and effective test settings; environment identity hashes an explicit
allowlist and include-file contents. These are observational identities, not a
claim that every possible environment input is covered. Missing exports and
missing dependencies remain data gaps. Export/upload failures do not replace the
test result, and raw test logs and assertion text are not uploaded by this pilot.
At Vitest's run-start callback, before global setup and test execution, the
capture writes an incomplete export to the same observations.json path. Its
preExecution field contains recordedAt and a sanitized observation snapshot:
the checkout/configuration identity and invocation inventory, with unknown
outcomes and no dependencies. Completion replaces the export atomically with
final outcomes and dependencies, retaining the original preExecution snapshot
and context. Changes to configuration or include files during execution cannot
rewrite that frozen identity. A missing or invalid terminal receipt leaves the
incomplete snapshot available if the artifact upload runs.
If expanded dependency paths exceed the export limit, the capture first uses a
version-2 observation payload: one shared dependencyPaths table with integer
references in each file's dependencies array. Readers resolve those references
before graph selection or model scoring; missing edges remain null. Small
exports and frozen pre-execution snapshots keep version 1. Both versions remain
readable, including retained historical artifacts. The outer export stays version 1.
If the compact payload still exceeds the limit, the capture retains
terminal outcomes and the frozen snapshot with all dependencies set to null,
and emits a warning. Missing graph data still requires conservative selection.
If that smaller export also exceeds the limit, the incomplete snapshot remains.
Before launching each observed Vitest child, the shard wrapper also writes
launch.json. It records the actual checkout commit/tree and whether tracked or
untracked changes exist, the preflight comparison base commit/tree, and a
rename/deletion-aware diff. The comparison base is not necessarily the source of
a prior dependency graph; consumers must retain both identities independently.
Recording does not fetch Git history; an unavailable comparison commit is a data gap.
The file hashes the finalized command arguments and pre/post-shard include-file
contents without exporting absolute paths. A missing base, dirty checkout, or
unreadable input records a fallback reason. Every launch record includes
no-prior-snapshot: prior qualification happens later, when the reporter has
the native inventory. The launch record itself does not resolve priors.
The reporter freezes the launch file's SHA-256 hash when it initializes and
retains it as preExecution.launchInputs. Consumers must match the hash to the
same directory's uploaded launch.json, verify source/runtime identity, and
check that recording preceded reporter initialization. A missing or mismatched
record is a data gap. Both files use the existing sampled artifact upload;
launch-only artifacts can survive a child that exits before producing inventory.
Launch records are bounded to 1 MiB. Preparation is best effort and does not
change the test command or result when it fails.
The native snapshot follows configuration evaluation and native test selection;
the launch record precedes them. Its decision field freezes a graph proposal
before tests execute, using the full native inventory. scheduledFiles always
contains that inventory; the proposal cannot change execution.
Global setup can still change worker settings after capture. The local recording
timestamp is not artifact availability: a later consumer must also retain the
publication/download time before using an earlier run as predictive evidence.
For the canonical unit-fast configuration, preExecution.graphInputs records
a version-1 profile with separate configuration and environment hashes. These
omit resolved include membership and pre/post-shard include-file contents. They
retain native configuration sources, direct setup sources, declared execution
and transform settings, runtime identity, and an explicit environment allowlist.
Repository paths in settings are normalized before hashing; regular expressions
retain their pattern and flags. Unsupported profiles or nonserializable settings
produce null without discarding the exact-run observations.
These hashes allow comparison of declared graph inputs across different file
selections. They do not establish interchangeable graphs or outcomes: unit-fast
shares process state between files, and the captured environment and direct setup
sources are not an exhaustive dependency contract. Consumers must also compare
the repository, lock, project/pool, runtime, platform and dependency-root identity,
retain source/diff provenance, and measure overlapping-file graph differences.
The shadow selector still requires the original exact context. Matching graph
inputs never grants permission to omit tests.
Published prior snapshots
For sampled same-repository PRs and scheduled runs, preflight uses its existing read-only Actions permission to restore at most two observation archives from the five most recent completed scheduled main runs. Successful and failed workflows can contribute observations; cancelled runs cannot. An unrelated job failure does not erase complete file results, while missing, incomplete or failing file history still requires conservative selection. Restore verifies the exact run/attempt, source commit/tree, archive digest and paired launch/member hashes. The producer must have completed and published before the current run began. Restore has a shared ten-second deadline and does not retry requests. Missing history or unavailable APIs produce an empty bundle and preserve full execution.
The bundle is shared through a same-run artifact. Test jobs download it without additional token permissions. Each bundle binds the current workflow run, attempt and actual checkout commit/tree; the reporter also freezes the bundle hash. Preflight obtains the exact prior-to-current comparison from GitHub once per prior revision, including removed paths and both ends of renames. It verifies the prior is an ancestor. When GitHub's comparison reaches its 300-file cap, preflight compares one recursive prior tree with the checked-out current tree. The tree response must be complete and fit within 32 MiB; comparisons with 600 or more changed paths remain ineligible. This uses the same ten-second restore deadline and includes removed paths, rename endpoints, and file-mode changes. The native inventory callback consumes this bound comparison; it does not substitute the launch record's comparison base.
Exact configuration, lock, environment and runtime identities must match. The test checkout can remain shallow; no test process fetches history or calls GitHub. Unknown source, dirty checkout, stale bundle or incompatible inputs produce an explicit fallback. Different shard membership can still prevent a match; graph-input comparability alone does not relax the selector.
The frozen decision records prior provenance, changed paths, graph candidates and the unchanged scheduled inventory. These are prospective proposals, not measured omission savings. Include restore, download, comparison and export costs in the eventual net-benefit calculation.
Owned model rankings
When a compatible prior is available, the same inventory callback records raw
CART scores in decision.ranking. The source-controlled model in
scripts/ci-adaptive-model.json uses five structural features: basename match,
path-token overlap, same directory, observed dependency membership and the
logarithm of changed-path count. Recorded dependencies describe membership,
not direct edges or graph distance. Current outcomes and durations never enter
these features. Missing dependency evidence remains unknown for a model that
uses it; invalid or unavailable models produce null scores with reasons.
The record includes hashes of the actual model bytes, extractor source and scoring inputs, along with elapsed scoring time. Rows preserve the full native inventory and its order, including project and pool identity. There is no score threshold, network request, provider token use or change to test execution.
The initial tiny tree was trained on 120 rows from controlled source mutations, with separate owner groups reserved for evaluation. Its manifest records the corpus and row hashes. The 64-row held-out result had ranking AUC 0.98276, but its predictions coincided with dependency membership on that narrow corpus. These class-balanced leaf scores are not calibrated failure probabilities or evidence of improved selection over the graph. Natural regression coverage and net CI savings still require full-run comparisons.
The first readout compares usable-data coverage, native-Bun gaps, observed failures, fallback causes, and paired total job costs. Learned selection and automatic omission require a separate evaluation and rollout decision.
A passed or failed file is complete only after a terminal run receipt and all its cases complete without skips or pending cases. Interrupted, filtered, unhandled-error, and missing-result runs remain unknown. Failure-tail replays are ignored; conflicting receipts are rejected.
Current coverage
Resource-reporter users receive the additional versioned inventory and case
counts. This includes existing UI and diagnostic lanes. Ordinary Node shard CI
enables collection only for the sampled unit-fast invocations described above.
Other configs and native Bun-test execution do not emit these pilot exports.
Missing observations must never be interpreted as passing or irrelevant tests.
Keep historical graph and learned selection in shadow mode until representative full-run audits establish failure recall, graph completeness, and net savings including collection cost.
Qualified Markdown skipping pilot
Canonical pull requests in aggressive selection mode can omit nine qualified
Markdown tests from the conventional directory fallback. This applies only when
the change includes a Markdown source module and otherwise contains only test
budget allocations, regular JavaScript/TypeScript production sources under
src, extensions or packages, or companion tests with a verified separate CI process.
Companion tests must have one explicit, complete canonical owner outside the
shared Markdown worker. Same-worker tests, unknown owners, helpers, fixtures,
missing or symlinked companion sources, and tooling/configuration changes retain
full coverage. A changed production companion that affects a qualified test's
input fingerprint retains that test. Independently checked docs
keep their existing selection rules. Explicit owners, changed tests,
The stateful table test is outside this pilot. Scheduled, main and release runs retain their existing full coverage.
Ten pure Markdown test files run in the dedicated core-markdown owner. It
shares Node worker policy, hermetic environment setup, output redaction and
resource controls with other lanes, without their application transforms or
custom cleanup runner. The table test keeps its existing unit owner. New tests
keep the ordinary ownership rules until explicitly moved into this lane.
config/ci-adaptive-markdown.json records reviewed input fingerprints. The
selector uses its existing import graph to hash each test's current transitive
workspace inputs, plus the shared test configuration and dependency lock inputs.
Missing, changed or symlinked inputs retain the test. A new import changes the
importing module's fingerprint; an unqualified test is never omitted. This is
unchanged-input reuse, not a learned prediction or historical graph fallback.
The PR summary lists actual omissions after all selection rules are combined.
Set repository variable OPENCLAW_CI_NODE_SELECTION=full to disable the pilot
along with aggressive selection. Refresh fingerprints only after reviewing the
changed input closure and repeating full-versus-selected Node/Bun proof, including
an injected regression. A successful provider call does not qualify a test.
The Markdown worker reads Node execution flags independently of the test watchdog and its other-lane inventories. Updating an unrelated lane's test list does not invalidate Markdown qualification. Changes to the Node flag policy still do.
Plan in shadow mode
The selector consumes an observation export and an explicit current inventory.
It writes a candidate plan; scheduledFiles always contains the full inventory.
Nothing in CI consumes the candidate as permission to skip tests.
The request is a version-1 JSON object:
| Field | Meaning |
|---|---|
context |
Current source and configuration, with the same fields as the export context. |
baselineRevision, baselineTree |
Exact source used as the base of changedFiles; must match the observation export. |
files |
Complete current inventory of {file, project, pool} objects. Include new tests. |
changedFiles |
Repository-relative changed paths, including deletions and both sides of renames. |
audit |
Required boolean. true selects the full inventory for an audit. |
alwaysRun |
Optional array of mandatory test file paths. |
Configuration mismatch, a wrong diff base, global test infrastructure changes, and unmapped changed paths select everything. New tests, incomplete evidence, previous failures, and missing dependencies keep the affected test selected. Every candidate carries its reasons. File counts are not runtime savings.
An optional --recommendations recommendations.json accepts
{version: 1, context, selectedFiles: [{file, project, pool}]}. Its context must
match the current source and configuration. Recommendations only add tests;
an empty or negative model recommendation cannot remove graph-selected tests.
This allows a local tree model or hosted decision model to share the same
experiment interface without making model predictions authoritative.
The caller owns inventory completeness, diff construction, provenance, and any external model invocation. The command does not call a model or send source to a service. Historical graphs cannot prove dynamic filesystem or generated-input coverage; full-run comparison remains necessary before enabling actual skips.
Evaluate recorded decisions
Compare an exported capture's frozen graph and model scores with its later outcomes:
This command performs no inference or test execution. It joins files by their project, pool and path, including when ranking rows have a different order. Missing predictions remain unavailable, and incomplete files remain unknown. The report identifies failed files proposed for omission, recorded score buckets and summed complete-file durations. Failures remain unattributed; they are not automatically causal regression labels or independent episodes.
The evaluator rejects inconsistent source/inventory identities and recording times. The caller must independently verify source and prior artifact provenance. Summed file durations are not elapsed time, CPU time or savings.
Estimate cost and compute savings
Supply a version-1 scenario with mode: "shadow" or mode: "projection", a
jobs array, and modelUsage. Every job represents one actual execution attempt;
include retries with distinct IDs. Measure the baseline and state projected
inputs explicitly. A job has these fields:
| Field | Meaning |
|---|---|
id |
Unique execution attempt identity. |
baselineSeconds |
Measured whole-job allocation duration. |
selectableSeconds |
Portion of that duration affected by test selection. |
retainedFraction |
Estimated fraction of selectable time retained, from 0 to 1. |
auditRate |
Fraction of executions that run all tests, from 0 to 1. |
overheadSeconds |
Added collection, planning, and scoring wait per job. |
auditOverheadSeconds |
Extra setup/work on full-run audits. |
dropEmptyJob |
Whether a zero-test selection can eliminate fixed job setup. |
usdPerMinute |
Dated price for this provider, platform and runner size; null if unknown. |
billingQuantumSeconds |
Actual billing rounding unit when the rate is known. |
allocatedVcpus |
Known allocated CPU count, or null; not CPU utilization. |
The model retains fixed setup, adds overhead, and prices selected and full-audit executions separately before averaging. It assumes the selected-work fraction scales that job's selectable allocation time; validate this with real paired runs. File-count reduction alone is not enough. Shadow mode retains all test work and only adds collection/scoring overhead.
modelUsage.baseline and modelUsage.candidate each contain inputTokens,
outputTokens, inputUsdPerMillion, and outputUsdPerMillion. Include all
requests and retries. Missing usage or prices stay unknown; use explicit zeros
for a measured no-model baseline. Ordinary unit tests consume no model tokens,
so introducing hosted selection increases tokens. Token savings require an
explicit metered baseline, such as replaced scoring requests or live-model tests.
Outputs distinguish runner seconds, allocated vCPU-minutes, runner list cost, model tokens, and model list cost. Negative savings are retained. Prices are scenario inputs, not invoice predictions; credits, discounts, minimums and storage charges need separate accounting. Workflow critical-path savings stay unknown until a dependency timeline is available. Do not label summed job time as developer wait time.
Declared file inputs
Prefer module imports for JSON fixtures so Vitest records their dependencies
without additional rules. Binary fixtures read through node:fs use the exact
test-to-input mapping in test/vitest/vitest.declared-inputs.ts. The shared Vite
plugin registers these inputs with native dependency recording and related-test
selection. It does not intercept file reads or change the scheduled inventory.
Undeclared file reads remain a graph coverage gap.
Optional Jev ranking experiment
node scripts/ci-adaptive-jev.mjs --request selection-request.json --output jev-ranking.json
accepts the existing selector request fields version, context, files, and
changedFiles. Supply TYPESAFE_API_KEY only to this trusted command. Without
it, the command writes null scores and keeps every scheduled file without making
a request. Ordinary CI does not invoke this command or receive this credential.
With a key, the command sends only changed paths and test identities to the
TypeSafe System One endpoint, using
the pinned jev-1.14.0 model. It does not send source, outcomes, logs, environment
values or conversation history. Each batch contains at most 25 opaque question
IDs; requests and streamed responses are limited to 64 KiB, with a single
ten-second deadline and no retries or redirects. At most 1,000 tests and 600
changed paths are accepted. Invalid or incomplete responses discard all scores
and preserve the full inventory.
These are path-only experimental scores, not calibrated regression probabilities.
The artifact records its actual inference time, input hash and reported token
usage. A failed request can leave total usage unknown; usage.complete makes
that explicit. Keep this artifact separate from frozen pre-execution decisions:
a later replay must never be presented as a prospective prediction. Live model
quality and billing need a key-backed experiment before any scheduling decision.