Skip to content

ADFA-5253 | Persist model URI instead of copying multi-GB models - #84

Open
jatezzz wants to merge 2 commits into
mainfrom
feat/ADFA-5253-persist-model-uri
Open

ADFA-5253 | Persist model URI instead of copying multi-GB models#84
jatezzz wants to merge 2 commits into
mainfrom
feat/ADFA-5253-persist-model-uri

Conversation

@jatezzz

@jatezzz jatezzz commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

Implemented direct-access behavior for LLM models selected via the Storage Access Framework (SAF). The application now retains persistent read permissions for the chosen URI and queries the .gguf file in-place. This prevents copying multi-gigabyte models into the app's internal sandbox, drastically saving device storage and speeding up the setup process. Additionally, a cleanup routine was added to delete legacy model copies, and the plugin's HTML documentation was updated to reflect this direct-access behavior.

Details

  • Updated LocalLlmBackend to hand the native loader a file descriptor path (/proc/self/fd/N) for direct reading.
  • Added ModelSourceWatcher and NativeModelSource interfaces to safely monitor, open, and evict models if the underlying source file is deleted or unmounted.
  • Adjusted LocalLlmSettingsViewModel and LocalLlmSettingsFragment to properly display and handle "Unavailable" model states when a URI becomes unreachable.
  • Refactored GgufModelInspector and ModelLoadDiagnostics to operate on InputStream factories rather than static file paths.
document_4956368585724266828.mp4

Ticket

ADFA-5253

Observation

To facilitate mmap operations in the native code without copying the file, ContentNativeModelSource resolves the content:// URI to a file descriptor and passes the /proc/self/fd/ path to the llama.cpp backend. A legacy cache cleanup (deleteLegacyModelCache) is executed in the background upon initialization to reclaim space from older app versions.

…hable

ADFA-5253: read the model through a held descriptor instead of copying it, and persist the picker's read grant. The settings pane derives its model and engine status from a live readability check, so a deleted file no longer reads as ready.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@itsaky-adfa itsaky-adfa left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 426e5bd. One confirmed defect on a path a user will hit, so requesting changes; the rest are minor. The refactor itself is careful, and the seam design (NativeModelSource / ModelResidencyEngine / ModelSourceWatcher) is what makes the residency rules testable at all - that part is a clear improvement.

Verified locally: ./gradlew testDebugUnitTest in ai-agent-local - 137 tests, 0 failures. git fetch origin first, so the diff is against current origin/main (68abdd6).

Blocking: the isAvailable() memo (LocalLlmBackend.kt:243) makes the first message after a restored model file fail with Backend 'local' is not available. Traced through ai-core's LlmInferenceServiceImpl.generateStreaming, which returns that string verbatim on a false.

Two things only a device can settle, both called out inline:

  • A streaming DocumentsProvider hands back a pipe, which the three openStream() calls consume before llama.cpp reads it. Worth picking a .gguf from Drive to see what happens.
  • Re-opening /proc/self/fd/N is a fresh path-based open(), checked against path permissions rather than the SAF grant that produced the fd. The attached video covers the happy case; an SD-card and a FUSE-volume load would close it. du -sh .../files/llm-models proves no copy was made, not that the load works everywhere.

Checked and cleared: descriptor lifecycle in ensureModelLoaded (the adopted/finally pairing and the unload-before-close ordering are right, and both header readers .use their streams); double-close() on modelWatch (unreachable - stopWatching nulls it); the close()-vs-onModelSourceGone race on cleanupScope; and no remaining callers anywhere in the repo of the removed or privatised API (engineState, savedModelPath, modelLoadingState, getSavedModelName, fallbackDisplayName, isGguf(String), EngineState.Uninitialized).

Two candidate findings I dropped after checking:

  • "The model is permanently unavailable once the memo is set" - false. LlmInferenceServiceImpl.getAvailableBackends() does not filter on isAvailable(), so local stays selectable and the warm-up clears the memo. Only the one-shot stale false survives, which is the blocking finding above.
  • "diagnose() losing FileMissing dropped its test" - false. givenMissingFile_whenDiagnosed_thenFileMissing was ported to givenAFilesystemPath_whenDiagnoseUnopenable_thenFileMissing, and diagnose now only runs after a successful open, so the narrowing is deliberate.

No prior review comments on this PR, so there was nothing to re-check from an earlier round. This repo has no written approve/request-changes rule, so the reviewer default applied: any confirmed IMPORTANT blocks. Its CLAUDE.md does require device-level verification over a green build, which is what the two items above are asking for.

Docs are in step with the code - ai-agent-local.html, assets/docs/index.html and the plugin.permissions comment all describe read-in-place rather than the old copy.

Drop the stale isAvailable() memo, refuse a non-seekable descriptor as
SourceNotSeekable, key the pane's unavailable marker off engine status,
coalesce watch notifications, and cover openDocument + the grant lifecycle.
@jatezzz
jatezzz requested a review from itsaky-adfa September 3, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants