Caching and tree sync
How a working tree reaches the cell, what goes into a cache key, and which targets a task runs on.
Tree sync
The CLI never sends a diff against a remote it may not have. It hashes the working tree as a Merkle tree of content digests (directory manifests over file digests), asks the cell which blobs it lacks, uploads only those, and submits the job with the root digest. The cell and the workers reassemble the tree from the content-addressed store.
- The first run of a repository uploads the tree once; from the second run only changed files and their directory manifests travel, inline with the submit.
- Build output never travels:
target/,node_modules/,.git/and friends are excluded by default,.gitignorefiles are honored with or without git, and.ciresk/ignore(gitignore syntax, anchored at the root) adds your own rules. - git is optional. With an
originremote the repository is identified by its URL; without one the folder gets a slug of its own, confirmed once and kept in.ciresk/repo. ciresk ci run check --dry-runprints what would be uploaded;ciresk ci doctorprints the tree, the ten largest files and the watcher state. A tree over its limits is exit 2 with the message naming.ciresk/ignore.
registered this folder as local/parser-experiment (kept in .ciresk/repo; edit it to rename)
tree: 1,204 entries, 38,120,331 bytes, root 7f3a9c1e2b44; walk 12 ms, hashed 1,204 (reused 0) 310 ms
uploading 1,288 blobs, 37,606,119 bytes ... done in 2,104 ms
job_01a158f2... check@HEAD trust trusted_interactive queued (position 1)To make every run one request, keep ciresk ci watch running in a second terminal: it re-hashes files as they change and pre-uploads them, so ciresk ci check finds nothing to send and reaches queued in the time of one round trip. A script that cannot keep a process runs ciresk ci watch --once --json right before the check.
The action cache
Every task result is stored under a key computed from its content, so an unchanged task on a second run is a cache hit: the verdict is reused and, when the task declares outputs, they are restored into the workspace. A task with cache: false always runs.
One SHA-256 over a canonical encoding of, in order:
| # | Item |
|---|---|
| 1 | The key schema version |
| 2 | The task's definition in the manifest that governs it: the manifest's path and the file with every task this one does not reach through depends_on left out, so its own fields, its dependencies' definitions, toolchains, globals and workspace count and an edit to another task does not. A manifest the worker cannot read that way counts as a whole |
| 3 | The resolved toolchains map (name and exact version) |
| 4 | The digests of every file matched by globals.inputs |
| 5 | The digests of every file matched by the task's inputs, on the base commit plus your diff: matched against git's index plus every untracked file no .gitignore excludes, so build output your .gitignore names (target/, node_modules/) never matches |
| 6 | globals.env and the task's env, as sorted name and value pairs |
| 7 | The run string after targets_arg substitution, plus shards, the shard index and the keys of depends_on tasks |
| 8 | The worker platform (os, arch) |
With trace_inputs, the key a task's result is stored under also folds in the files the task read and the tracer version.
Never in the key: pass_env values, secrets, who submitted, the organization (it is the namespace the key is stored under), timestamps, the job id. Two agents with the same tree and the same task share one key and one cache entry.
Keys are write-once: a second write with different content is refused and logged. Entries record their writer's trust tier, and reads filter by the reader's allowed set: trusted_ci is the only writer every tier reads, trusted_interactive reads its own tier and trusted_ci, and untrusted reads trusted_ci and writes to an isolated namespace per job. Restores validate paths: no absolute paths, no .., no symlink escape. See Workers and trust.
Targets
With no targets, the cell computes the affected set: it diffs your tree against the tree of the newest job of the same kind that succeeded on your branch with no explicit targets (the repository's newest other tree when your branch has none), maps the changed files to targets through each task's affected.strategy, and adds each target to run through the task's targets_arg. ciresk ci affected shows what would run. The task runs on its full target set instead when a file in globals.inputs, the governing .ciresk/ci.yaml or a toolchains entry changed, when the task says strategy: none, when its dependency graph cannot be read, when the diff touches more than 5,000 files, or with --full; the summary names the reason.
Affected selection narrows only tasks with a targets_arg; for every other task only its inputs (the cache key) save cost.
Targets you pass (ciresk ci check -p <crate>) skip the computation and reach run the same way; without a targets_arg, run is executed unchanged.
--full asks for every target: it needs the allow_full_build policy and the ci:jobs.run_full scope, and is refused with exit 4 otherwise.
Warm workspaces
A warm workspace is a persistent directory per repository and slot under a fixed path on the worker (/var/lib/ciresk-ci/workspaces/warm/<repo_id>/, identical on every worker because Cargo's incremental state is bound to the path). Each job brings it to your tree, writing only the files that changed, and keeps .ciresk/ and the manifest's workspace.keep globs; target/, node_modules/ and the package stores survive between jobs, which is what makes a one-crate check fast. Only trusted_interactive tasks use it; trusted_ci and untrusted get a fresh directory. One task at a time per slot workspace. When a task needs more disk than is free, the worker evicts idle warm state least recently used first (an idle slot's target/, then whole workspaces), never one a running task or a task about to start holds; a later job on an evicted slot builds cold once. The room a worker offers the queue counts that idle build output as free, so a full disk of old target/ directories does not keep a task waiting.