anchored-install (ai)
anchored-install runs a plain list of shell commands and caches every step
that succeeds. When a run fails, you fix the problem and re-run — already-succeeded
steps are skipped and execution resumes from the first unfinished step, instead
of starting over from the top every time. Think Dockerfile layer caching, but for
an ordinary shell script.
A manifest is just a normal shell script: the caching markers are #ai comments,
so bash install.sh runs the exact same file natively, with or without ai
installed. This is the tool's core appeal — keep one file, get caching for free.
When to reach for this
Once the trigger condition above is met, reach for anchored-install for checkpointed retries: completed shell steps should be cached and a failed or interrupted run should resume without repeating them. This also applies when an existing installation has already failed repeatedly and the user wants to preserve its completed progress. The payoff is biggest for long, effectful sequences such as installing packages, cloning and building repos, downloading large artifacts, running migrations, or provisioning a machine.
When the task is to author a new install/setup/provisioning script, infer the
need proactively when restarting from the top would make several expensive or
failure-prone steps run again, even when the user did not say “cache” or “resume.”
Add the #ai markers from the beginning; do not wait for the first failure. This
inference applies to a script we are designing, not to wrapping or executing an
existing vendor/third-party installer.
For a plain first-time vendor/third-party installer, package-manager command, or
short script, use the ordinary installation workflow. After repeated
interruptions, move to ai when rerunning would redo completed work, even if the
user did not name the tool. A single installer error is an ordinary debugging
task; it does not by itself establish a need for ai. Caching overhead and
on-disk snapshots are not worth adding when rerunning everything is cheap.
The mental model (read this before authoring)
Three ideas explain everything the tool does:
- Each non-comment logical line is one step. Steps are the unit of caching.
- Cache keys are chained (Dockerfile-style). A step's key folds in the
previous step's key:
So editing step i invalidates step i and everything after it, while stepskey[i] = sha256(key[i-1] + "\n" + normalized(step[i].text))0..i-1stay cached. Appending new steps at the end re-uses all earlier caches. This is why step order matters — see "Order steps for cache reuse". - Environment carries across steps, even cached ones.
cdandexportpersist between steps because after each successaisnapshots the delta it made to the environment and working directory. When a cached step is skipped, its delta is replayed, so later steps still see the right$VARSandpwdeven though the step didn't actually re-run.
Only step text feeds the hash. Comments and blank lines are stripped before hashing, so reformatting or rewording a comment never busts the cache.
Authoring a manifest
Write commands one logical step per line. Give the file its native shell
extension (.sh on Linux) so it stays a runnable bash script. The parsing rules:
- One logical line = one step. A trailing
\continues a line, so a single step can span physical lines:echo "downloading..." && \ curl -fsSL https://example.com/x.tar.gz -o x.tar.gz && \ tar xzf x.tar.gz - Comments (
# ...) and blank lines are ignored and never affect the cache. - Multi-line shell constructs must be wrapped in a block. An
if,for,while,case, or heredoc spans several lines that together form one step — collapse them with#ai >>>…#ai <<<:
Without the block, each physical line would be parsed as its own step and the construct would break. Whenever you write a multi-line construct, wrap it.#ai >>> if ! command -v node >/dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs fi #ai <<< #ai: alwaysmarks the next step to run on every invocation, even when everything else is cached. Use it for effects that don't gate later steps — logging, banners, timestamps, "current status" echoes.#ai: name <label>attaches a human-readable label to the next step, which shows up inai status/ai planoutput. Helpful for long manifests.
Directives attach to the next step, not the next line — intervening comments
and blanks are fine. Unknown #ai: directives are a hard error, as is an unclosed
#ai >>> block, so the only directives that exist today are always and name.
Order steps for cache reuse
Because editing a step invalidates everything downstream, put the stable, slow-changing steps first and the volatile ones last. Concretely:
- System packages and toolchain installs (rarely edited) go near the top.
- The line you're most likely to tweak (a build flag, a repo URL, an app-specific command) goes as late as possible, so editing it doesn't throw away the expensive earlier layers.
- Prefer appending new steps at the end; that re-uses every existing cache.
This is the single highest-leverage authoring decision — it's the difference between a re-run that resumes near the end and one that rebuilds half the script.
Starter template
A ready-to-edit manifest lives at assets/template.sh in this skill — copy it and
adapt rather than writing from scratch. It demonstrates a block step, a backslash
continuation, env carry-over, and an #ai: always step.
Running a manifest
bash install.sh # plain native run, no caching (the #ai markers are comments)
ai run install.sh # execute with caching; cached steps are skipped
ai status install.sh # show which steps are cached vs pending
ai plan install.sh # dry-run: parsed steps + cache keys, nothing executed
ai clean install.sh # forget cached progress for this manifest
Start with ai plan to confirm the steps parsed the way you intended (this is the
fastest way to catch a multi-line construct that wasn't wrapped in a block — it'll
show up as several broken steps). Then ai run.
The resume-after-failure workflow
This is the whole point of the tool, so lean into it:
ai run install.shexecutes steps in order, printingRUN/CACHEDper step.- On the first failing step,
aistops and tells you which step and the exit code. Everything before it is now cached. - Fix the cause (install the missing dependency, correct the command, set the
env var) and run the same
ai run install.shagain. Cached steps are skipped and execution resumes exactly at the step that failed.
Do not reach for --no-cache or ai clean after a normal failure — that
throws away good progress and defeats the purpose. Only rebuild from scratch when
the cached state on the machine is actually wrong (see Caveats).
ai run flags
--no-cache— ignore existing cache for this run (still records fresh results).--from N— force re-run from step N (1-based) onward.--workdir DIR— starting directory (default: the manifest's own directory).--shell PATH— shell used to run steps (default:$AI_SHELLor/bin/bash).--no-env-snapshot— run each step as an independent subprocess;cd/exportno longer carry across steps. Use this if you'd rather not persist env snapshots to disk (see secrets caveat below).
Cache lives under ~/.cache/anchored-install/<manifest-id>/ (mode 0700), one
snapshot file per succeeded step.
Caveats & gotchas (warn the user about the relevant ones)
- Success is trusted — the cache is the state. anchored-install has no "is it
actually installed?" check; a step that exited 0 is considered done forever. If
an external effect is later undone by hand (someone removes the package you
installed),
aistill skips that step. Rebuild withai cleanorai run --no-cachewhen the real machine state has drifted from the cache. #ai: alwayssteps re-run but their text is unchanged, so their cache key — and every downstream key — is identical. Downstream steps may still be served from cache. Usealwaysonly for effects that don't gate later steps (logging, banners), not to force a real rebuild.- A failing
&&/||list at the end of a step is a failure. A standalone step likegrep pattern file(or any&&chain) whose final command returns non-zero stops the run. If a non-match is acceptable, append|| true. - Env snapshots touch disk and can contain exported secrets. They're written
under a
0700cache dir, but if a step doesexport TOKEN=..., that value is persisted in the snapshot. Use--no-env-snapshotwhen that's a concern (you losecd/exportcarry-over in exchange).
Constraints
- Requires Python ≥ 3.8 and a POSIX shell (
/bin/bashby default). Zero third-party dependencies — pure standard library. - Tested on Ubuntu with bash. Other shells/platforms (and parallel steps,
remote/container backends, Windows/PowerShell, manifest
include/templating) are planned but not yet available — don't promise them. - Install with
pip install anchored-install(provides both theaiandanchored-installcommands).