microsoft / microsoft/foundry-local
no API to remove a cached model, and the only handle the SDK gives you doesn't cover all of the model's state.
- Dominant language
- C++
- Stars
- 2.6k
- Forks
- 369
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 39
Description
## Summary
The SDK can download a model and tell you whether one is cached, but it cannot remove one.
An application that wants to offer "clear cached model", which is the natural remedy when a
download has gone wrong, has to delete files itself.
The only handle the SDK offers for that is `GetPathAsync`, and it is not sufficient. It
returns the **revision** directory, while some of the model's state lives above and beside
it. Deleting exactly what you were given therefore leaves state behind, and the leftover
state is read back on the next download.
This matters more than a missing convenience API would, because clearing the cache is the
remedy users are told to try when a model is corrupt. If the remedy is incomplete, the
corrupt state can survive it.
## What we hit
Our application has a "clear cached model" button. It called `GetPathAsync`, deleted that
directory, and reported success. The user then started the download again, and the progress
indicator resumed at the percentage from the previous interrupted attempt instead of
starting from zero.
## Why, from the on disk layout
The cache is laid out as:
```
%USERPROFILE%\.\cache\models\
foundry.modelinfo.json <- shared across all models
\\
.download.progress <- model directory
v3\
.download.state <- beside the payload
manifest / config files
```
`GetPathAsync` returns the `v3` revision directory. We confirmed this from the manifest it
produces: the keys are flat file names with no `v3/` prefix, so the returned root is the
revision directory and not the model directory.
That means deleting the returned directory removes the payload and the per blob
`.download.state`, but leaves:
- **`.download.progress`**, in the model directory one level above. In our case it still
contained the stale percentage from the interrupted attempt, which is what the UI read
back.
- **`foundry.modelinfo.json`**, at the `cache\models` root and shared by every model. We
have not confirmed whether a stale entry there also affects `IsCachedAsync`, but it is
clearly not something an application should be reaching into.
To be precise about what we verified: we confirmed that the progress marker survives the
delete and that the reported progress resumes from its stale value. We did not measure
whether previously transferred bytes are re-downloaded.
## Why "just delete the parent directory" is not the answer
That is what we ended up doing, guarded so that it can only ever walk up to a directory
whose children are all revision folders, and falling back to the old behaviour otherwise.
It works, but it is an application guessing at an internal layout, which is exactly what an
SDK is meant to prevent. Every application that offers this will guess differently, and any
change to the layout breaks all of them silently, because deleting too little fails quietly
rather than throwing.
## What we would like
1. **A real removal API**, for example `DeleteModelAsync(alias)`, that removes every piece
of state associated with a model: payload, per blob download state, progress markers, and
any catalog or manifest entry, and that leaves `IsCachedAsync` returning false
afterwards.
2. If that is not available soon, **document the cache layout and the state files**, and
state explicitly what `GetPathAsync` returns, so applications that must do this by hand
can at least do it correctly.
3. **Keep all per model state under a single root**, so that removing the directory the SDK
hands back is by construction complete. Today the progress marker sits above it, which
makes the obvious implementation the wrong one.
4. **Do not trust a leftover progress marker on its own.** Resuming should be conditional on
the payload and the per blob state actually being present, so a partially removed cache
starts clean instead of reporting progress it cannot substantiate.
## Related
This is the missing half of the corruption issue at
https://github.com/microsoft/foundry-local/issues/906. That issue is about a download that
is interrupted by machine sleep and leaves a file at its full final length with the
remainder reading as zeros. Clearing the cache is the obvious way for a user to recover from
that, so removal needs to be both available and complete.
Contributor guide
Research direction
Start by tracing the SDK entry points GetPathAsync and IsCachedAsync, then inspect how model payloads, .download.progress, per-blob state, and catalog metadata are located. Define removal behavior that covers all model-owned state and leaves IsCachedAsync false, while considering the related corruption issue 906. Done means applications no longer need to infer the cache layout and interrupted downloads do not retain stale progress.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- api
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100