| name | anchored-install |
| description | Author and run resumable, cached install/setup scripts with the `anchored-install` (`ai`) tool — a backend-agnostic installer that gives plain shell scripts Dockerfile-style layer caching, so a failed re-run resumes from the last successful step instead of starting over from the top. Use this whenever the user wants an install, setup, bootstrap, provisioning, or environment-setup script that is resumable, restartable, idempotent, re-runnable, or cached — or that "shouldn't redo everything that already worked" when it fails and is re-run — even if they never mention anchored-install or `ai` by name. Also use it when writing or editing `#ai` manifests, debugging a manifest that failed mid-run, or converting an existing install.sh / setup script into an anchored-install manifest. |
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
Use anchored-install when the work is a sequence of slow, effectful steps that
you don't want to repeat on a re-run: installing packages, cloning and building
repos, downloading large artifacts, running migrations, provisioning a machine.
The payoff is biggest when steps are expensive and failures are likely (flaky
network, missing sudo, a typo three steps in).
It is not the right tool for fast, pure, or already-idempotent scripts where
re-running everything is cheap — there the caching overhead and on-disk snapshots
aren't worth it. Say so if a user asks for it in that case.
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:
key[i] = sha256(key[i-1] + "\n" + normalized(step[i].text))
So editing step i invalidates step i and everything after it, while
steps 0..i-1 stay 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.
cd and export
persist between steps because after each success ai snapshots 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 $VARS and pwd
even 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 <<<:
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
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: always marks 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 in ai status / ai plan output. 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
ai run install.sh
ai status install.sh
ai plan install.sh
ai clean install.sh
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.sh executes steps in order, printing RUN / CACHED per step.
- On the first failing step,
ai stops 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.sh again. 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_SHELL or /bin/bash).
--no-env-snapshot — run each step as an independent subprocess; cd/export
no 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),
ai still skips that step. Rebuild with ai clean or
ai run --no-cache when the real machine state has drifted from the cache.
#ai: always steps 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. Use always only 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 like grep 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
0700 cache dir, but if a step does export TOKEN=..., that value is
persisted in the snapshot. Use --no-env-snapshot when that's a concern (you
lose cd/export carry-over in exchange).
Constraints
- Requires Python ≥ 3.8 and a POSIX shell (
/bin/bash by 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 the ai and
anchored-install commands).