awa run — deterministic run cache
On this page
awa run [flags] -- <command> [args...]
awa run wraps a command. On the first run it executes the command, captures
its output and observed state, and — if the run is clean and non-mutating —
stores a reusable cache entry keyed on the inputs the command could see. A later
run with the same key replays the stored result instead of executing again.
Result model
hit— a stored result was replayed; the command did not execute.miss— the command executed; the result may be stored if cacheable.uncached— the command executed but the result was not made reusable (e.g. skipped inputs without--allow-skipped-inputs).
Stdout and stderr are captured separately; their cross-stream interleaving is
neither recorded nor promised. Every hit returns the stored child exit. Outside
--json, --display full writes each stream's exact bytes, complete stdout
before complete stderr; bounded, hidden, and JSON displays claim no full byte
replay.
Cache key
The cache key covers, at a high level: the command, the run input scan (files
the command could read), the working directory relative to the project root, and
the relevant environment. awa run is cwd-sensitive by design — the same
command launched from a different directory is a different key. Make it explicit
with:
awa run --cwd ./packages/api -- npm test
Trust mode
How closely the input scan compares files is the trust mode, set by
[hashing].trust_mode and overridable per invocation with the global
--trust-mode <mode> (normal, strict, or fast), or with --strict as a
shorthand:
normal— the default balance of hashing and stat signatures.strict— always hash content, never trust size and modification time. Slower, and the right choice when a file may change without its metadata changing.fast— stat-only comparison. It can miss a same-size, same-mtime rewrite, so a run observed under it is recorded but never published as reusable (fast-trust-mode).
Trust mode is part of the cache key, so changing it does not silently reuse results observed under a different one.
Mutation guard
awa run returns the wrapped command's own exit code after a hit or a miss, and
a mutating or failed execution is still recorded as history. But only a clean,
non-mutating run becomes a reusable hit — if the command changed observed state,
the result is returned but never published for reuse. The cache prefers a false
miss (re-run unnecessarily) over a false hit (skip work that mattered).
Effect observation
awa run also watches generated-output roots with a bounded stat signature. The
watched set is the built-in one — every baseline exclude, such as node_modules
and target, plus build, dist, out, and coverage — plus any
[run].extra_effect_roots, selecting by name at any depth or by exact
project-relative path. That second built-in group is watched though the input scan
still sees it: a build artifact is both a real command input and generated state.
.awa/ and .git/ are the exception: neither scanned nor watched, because awa's
own state and your VCS metadata are outside its guarantee.
Note the boundary, because it is the one place excluding a path can cost you a correct result: an exclude you add yourself is not watched, so a replay can report success while an excluded, unwatched directory is missing. "Effect roots vs excludes" below turns that into a decision.
Deleting or changing that generated state after a reusable run misses instead of
falsely hitting (reason effect-state-differs); a watched root that cannot be
observed safely, or a run under the fast trust mode, records but never becomes
reusable (effect-state-unavailable, fast-trust-mode). A cache hit is reusable
for observed inputs and configured project-local observation policy; by default
it does not track files outside the project root, and never network services,
clocks, or home-directory config.
Latency diagnostics
awa explains slowness it can name and says nothing otherwise: a note appears only past the shared interactive threshold, at most two per invocation. Neither cause is a misconfiguration; neither is disableable.
large-effect-root(run.effect-observation, hintrecord-mode) — a watched generated-output root dominated state observation.full-input-rehash(run.input-observation, hintreview-run-scope) — reading the inputs was slow enough to notice; a hit pays it too.
Both appear on run, run explain, run ls and status, and in JSON under
data.diagnostics.performance[]. For the note and hint text, what each count
means, and the safe response to each, see awa help troubleshooting.
Display
Display affects this invocation's terminal output only — never capture, the cache key, or the exit code:
--display full default; stream/replay the full output
--display summary metadata plus a short excerpt
--display tail:<n> the last n lines
--display none awa diagnostics only, no command output
Useful flags
--refresh ignore any existing hit, publish a fresh result
--no-cache neither read nor write the cache
--no-cache-failures execute, but do not cache a failed result
--allow-skipped-inputs allow caching when the input scan skipped files
--cwd <path> set the execution directory (part of the key)
Footer and inline diagnosis
Every real execution and hit ends with a compact footer: the storage state (hit/stored/record-only/uncached), the run id, cwd, exit, duration, whether the result is reusable, and the command to inspect it. A mutating or otherwise non-reusable run also prints the exact before/after compare command.
For a common miss the footer also shows an inline cache diagnosis: the nearest
previous run, why it is not reusable (e.g. input-tree-differs), a bounded
sample of the changed inputs, and the exact run explain / run ls --near
follow-ups — so the first response is useful without a second command. It is a
bounded preview, not a replacement for run explain / run ls --near.
Why a run is not reusable
Every surface that reports reuse — awa run's footer, run ls, run ls --near,
run explain, and a stored run's recorded reuse state — names the cause with one
token from a single closed vocabulary. This is that vocabulary, grouped by what
the token is a statement about.
The observed state no longer matches what was recorded:
input-tree-differs the observed input tree changed
mutated-state the command changed observed state
effect-state-differs watched generated output changed or was deleted
effect-state-unavailable a watched root could not be observed safely
fast-trust-mode observed under fast trust; recorded, never reusable
A recorded key fact no longer matches:
expired older than the run TTL
env-mismatch an allowlisted environment fact differs
cwd-mismatch the execution directory differs
config-mismatch another keyed fact differs (scope, trust mode, ...)
platform-mismatch recorded on a different OS or architecture
The stored evidence cannot be replayed honestly:
payload-missing the stored output is absent or fails its hash
corrupt the stored metadata could not be decoded
post-scan-failed the post-run observation scan failed
Policy declined to make the run reusable:
non-cacheable-policy caching was forbidden for this invocation
record-only captured as supervised history, never offered
no-cache ran without reading or writing the cache
stdin-not-keyed piped or tty stdin is not part of the key
skipped-inputs the scan skipped inputs without the allow flag
capture-disabled output capture was off, so nothing was stored
failed-not-cached the command failed and policy declines to cache it
Only input-tree-differs can produce a changed-input-path sample, because only
that comparison has two input manifests to diff. The tokens are stable machine
values: switch on them rather than on the surrounding prose.
Inspect and explain
awa run ls current-state reuse view (reusable right now)
awa run ls --near also list near misses and why they are not reusable
awa run log chronological run history, newest first
awa run show --last inspect a run's metadata and captured output
awa run explain -- <command> compute the key and cache decision without running
run ls --near explains a stale check: it adds a "near misses" section naming,
for each recent non-reusable run, the mismatch reason (e.g.
input-tree-differs) and a bounded sample of the changed input paths.
run explain reports the same reason.
Add --json to any of these for a stable machine form; run ls --json and
run explain --json report the same facts and share one near-miss shape. See
awa help json.
Evidence and privacy
Captured stdout/stderr is stored verbatim as durable evidence, so a command that
prints a secret stores it — awa does not redact output. Allowlisted env values
are keyed and passed to the child but stored only as a redacted identity, never
raw. If output exceeds the capture limit it is stored truncated and every
surface (footer, replay, run show) says so with the omitted byte count, so a
partial payload is never mistaken for the full output.
The child does not inherit your environment: it gets the built-in baseline (your
locale included, unchanged), what env_allowlist adds, and the fixed advisory
marker AWA_RUN=1 — all keyed. See awa help privacy.
Effect roots vs excludes
The central run-cache decision:
- The command writes or refreshes a generated directory during the run,
and that output is disposable — it may safely be absent after a replay
→
[run].extra_excludesor.awaignore. Otherwise the self-generated output makes every run non-reusable. - The command writes output you actually need on disk afterwards
→
awa run --record. Excluding it would let a replay report success with the output missing. - The command only reads an already-produced generated directory that the
run input scan no longer sees →
[run].extra_effect_roots. Later changes to that generated state should invalidate reuse. - That directory is still visible to the input scan → configure nothing. Its contents already key the run; an effect root would add no coverage.
- The command is a deploy, migration, formatter, live probe, or otherwise
non-reusable →
awa run --record. Keep durable evidence without publishing a reusable hit.
Picking an effect-root selector (literal, never globs):
- The same directory name at any depth, typically repeated across
monorepo packages → a name:
extra_effect_roots = ["bin"]. - Exactly one location → a project-relative path:
extra_effect_roots = ["artifacts/bin"], which watches neitherother/binnorother/artifacts/bin.
Rules:
- Writing to a watched effect root during the run makes the result non-reusable.
- The two lists are separate: the watched set is the built-in effect roots
plus
extra_effect_roots. An exclude you add yourself is NOT watched, so an excluded, unwatched directory is invisible to the cache in both directions. - Excluding a path therefore weakens what
awa runcan observe, so do it intentionally. - awa will not auto-edit config to improve the cache hit rate.