Skip to content

feat: record cache entries in a per-notebook manifest - #10838

Merged
dmadisetti merged 2 commits into
mainfrom
dm/cache-manifest
Sep 24, 2026
Merged

dmadisetti merged 2 commits into
mainfrom
dm/cache-manifest

Conversation

@dmadisetti

Copy link
Copy Markdown
Member

Stacked on #10837; review against dm/cache-size.

📝 Summary

Every persistent cache hit or save now attests its entry in a manifest
beside the cache, grouped under a path hash: a static digest of the
defining cell and every cell it depends on, recomputable from source
alone (exposed as BlockHasher.path_hash and hash_cell_closure). Records
accumulate across runs and travel through the Store interface under a
reserved dot key, so remote and tiered stores carry manifests too, each
attesting only the entries stored beside it. Flushes coalesce to one
write per run (ON_FINISH hook, teardown retry); recording never fails
the user's cell. FileStore.put is now atomic (tmp sibling + os.replace)
and interrupted-write leftovers are excluded from stats and swept by
clear().

Also fixes unmangle_local for hyphenated cell ids (a UUID, or a nested
app's prefix): a mangled _private name was previously left mangled in
co_names, so kernel and load_app digests disagreed for such sessions.

@vercel

vercel Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marimo-docs Ready Ready Preview Sep 24, 2026 6:57pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 issues found across 18 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="marimo/_save/cache.py">

<violation number="1" location="marimo/_save/cache.py:93">
P2: When a custom `Store` is unhashable, manifest recording fails before any entry is recorded because `CacheState` keys records by the store object. Key manifest state by a guaranteed-hashable store identity or otherwise support unhashable `Store` implementations.</violation>
</file>

<file name="marimo/_runtime/runner/hooks_on_finish.py">

<violation number="1" location="marimo/_runtime/runner/hooks_on_finish.py:97">
P3: This hook duplicates `CacheCallbacks._flush_cache_manifest`, so future changes to manifest-flush guards or error handling can diverge between run completion and teardown. Extract the shared flush helper and call it from both lifecycle paths.</violation>
</file>

<file name="marimo/_save/manifest.py">

<violation number="1" location="marimo/_save/manifest.py:270">
P2: When two sessions flush the same notebook concurrently, the last whole-manifest write discards records from the other session. Serialize the read/merge/write operation or retry after detecting a changed manifest.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread marimo/_save/manifest.py Outdated
Comment thread marimo/_save/manifest.py Outdated
Comment thread tests/_save/test_manifest.py
Comment thread marimo/_save/cache.py
# Cache keys this session recorded, grouped by the store they were
# written through, then by path hash and block. A manifest lists only
# the entries stored beside it.
manifest_records: dict[Store, dict[str, dict[str, set[str]]]] = field(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When a custom Store is unhashable, manifest recording fails before any entry is recorded because CacheState keys records by the store object. Key manifest state by a guaranteed-hashable store identity or otherwise support unhashable Store implementations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_save/cache.py, line 93:

<comment>When a custom `Store` is unhashable, manifest recording fails before any entry is recorded because `CacheState` keys records by the store object. Key manifest state by a guaranteed-hashable store identity or otherwise support unhashable `Store` implementations.</comment>

<file context>
@@ -86,6 +87,16 @@ class CacheState:
+    # Cache keys this session recorded, grouped by the store they were
+    # written through, then by path hash and block. A manifest lists only
+    # the entries stored beside it.
+    manifest_records: dict[Store, dict[str, dict[str, set[str]]]] = field(
+        default_factory=dict
+    )
</file context>

Comment thread marimo/_save/manifest.py

key = manifest_name(ctx.filename)
try:
manifest = load_manifest(store, key) or CacheManifest()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When two sessions flush the same notebook concurrently, the last whole-manifest write discards records from the other session. Serialize the read/merge/write operation or retry after detecting a changed manifest.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_save/manifest.py, line 270:

<comment>When two sessions flush the same notebook concurrently, the last whole-manifest write discards records from the other session. Serialize the read/merge/write operation or retry after detecting a changed manifest.</comment>

<file context>
@@ -0,0 +1,339 @@
+
+    key = manifest_name(ctx.filename)
+    try:
+        manifest = load_manifest(store, key) or CacheManifest()
+    except UnsupportedManifestVersionError as e:
+        # A rewrite under the older schema drops whatever the newer one
</file context>

notebook produced, and is rewritten whole.
"""
del ctx
from marimo._runtime.context import safe_get_context

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This hook duplicates CacheCallbacks._flush_cache_manifest, so future changes to manifest-flush guards or error handling can diverge between run completion and teardown. Extract the shared flush helper and call it from both lifecycle paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_runtime/runner/hooks_on_finish.py, line 97:

<comment>This hook duplicates `CacheCallbacks._flush_cache_manifest`, so future changes to manifest-flush guards or error handling can diverge between run completion and teardown. Extract the shared flush helper and call it from both lifecycle paths.</comment>

<file context>
@@ -86,7 +86,25 @@ def _send_cancellation_errors(ctx: OnFinishHookContext) -> None:
+    notebook produced, and is rewritten whole.
+    """
+    del ctx
+    from marimo._runtime.context import safe_get_context
+    from marimo._save.manifest import flush_dirty_manifests
+
</file context>

Comment thread marimo/_save/stores/file.py
Comment thread marimo/_save/stores/store.py Outdated
@dmadisetti
dmadisetti marked this pull request as ready for review September 22, 2026 23:48
Copilot AI lite review requested due to automatic review settings September 22, 2026 23:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate findings affect manifest durability, consistency, pruning, and cleanup.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 4 High severity · 1 Medium severity

Open (5)
What changed in this PR

Adds per-notebook cache manifests, static closure hashing, atomic file writes, interrupted-write cleanup, and hyphenated cell-ID handling.

Changes:

  • Records cache hits and saves in Store-backed manifests.
  • Adds manifest flushing across cache lifecycles and storage tiers.
  • Adds atomic writes, partial-write handling, and regression tests.
File Changes and final review findings
tests/​_save/​test_manifest.py Manifest tests. Nit (1 vote): Update the contradictory docstring to describe the no-rewrite behavior.
tests/​_save/​test_hash.py Path-hash tests. No final findings.
tests/​_save/​test_cache_dirs.py Interrupted-write stats tests. No final findings.
tests/​_ast/​test_demangle_locals.py Hyphenated-ID demangling tests. No final findings.
marimo/​_save/​stores/​store.py Store interface documentation. No final findings.
marimo/​_save/​stores/​file.py Atomic file writes. No final findings.
marimo/​_save/​save.py Critical (1 vote): Cache entries can be recorded before asynchronous writes complete, allowing manifests to attest missing entries.
marimo/​_save/​manifest.py Moderate (1 vote): evict_dead can remove entries still attested by live nodes.
Moderate (1 vote): Embedded-app records may not reach a flushed manifest.
Critical (2 votes): Remote read failures can overwrite existing records.
Critical (2 votes): Concurrent read/merge/write operations can lose manifest updates.
marimo/​_save/​loaders/​loader.py Moderate (1 vote): Zero-byte temporary files are not swept.
Moderate (1 vote): Nested temporary files are missed during cleanup and counting.
marimo/​_save/​hash.py Static closure hashing. No final findings.
marimo/​_save/​cache.py Moderate (1 vote): Cache clearing does not invalidate session records, allowing deleted entries to be re-attested.
marimo/​_save/​cache_dirs.py Partial-write detection and statistics. No final findings.
marimo/​_runtime/​runner/​hooks_on_finish.py Critical (2 votes): Manifest flushing can occur before background cache writes finish, preventing teardown retry from correcting the manifest.
marimo/​_runtime/​executor/​lifecycles/​cached.py Moderate (1 vote): Successful ui_live cache hits are not recorded.
Moderate (3 votes): Cache hits are marked as saved and unnecessarily dirty the manifest.
marimo/​_runtime/​callbacks/​cache.py Teardown flushing. No final findings.
marimo/​_ast/​variables.py Hyphenated local-name unmangling. No final findings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +100 to +102
context = safe_get_context()
if context is not None:
flush_dirty_manifests(context)
Comment thread marimo/_save/manifest.py

key = manifest_name(ctx.filename)
try:
manifest = load_manifest(store, key) or CacheManifest()
Comment thread marimo/_save/manifest.py
manifest.notebook = _notebook_ref(ctx.filename, store)
manifest.written_at = _utc_now()
try:
written = store.put(key, manifest.to_bytes())
Comment thread marimo/_save/save.py
Comment on lines +375 to +376
if self.loader.save_cache(attempt):
self._record(attempt)
from marimo._save.manifest import record_cache_event

record_cache_event(
self._loader, attempt.key, cell_id, self._graph, saved=True
@github-actions github-actions Bot added documentation Improvements or additions to documentation bash-focus Area to focus on during release bug bash labels Sep 24, 2026
dmadisetti added a commit that referenced this pull request Sep 24, 2026
## 📝 Summary

Adds the `marimo cache` command group. This is the base of a seven-PR
stack; each PR targets the one below it.

1. #10836 `marimo cache dir` (this PR): the command group and the shared
PATH → cache-directory resolution.
2. #10837 `marimo cache size`: disk usage and entry count per cache
directory; fills the `-1` stubs behind the cache panel.
3. #10838 Per-notebook manifest: every persistent cache hit or save is
recorded under a static hash of the cell and its ancestors.
4. #10839 `marimo cache clean`: deletes a notebook's recorded entries,
or wipes a directory, after confirmation.
5. #10840 `marimo cache prune`: deletes only entries that current code
can no longer produce; never deletes an entry current code still
records.


Adds the marimo cache command group with its first subcommand, dir,
which prints the cache directories a PATH resolves to. A notebook file
resolves to the cache directory beside it. A directory resolves its own
__marimo__/cache, as if a notebook ran in that directory; with
-r/--recursive it is instead searched recursively, skipping dot-folders
and logging unreadable subtrees.
Base automatically changed from dm/cache-size to main September 24, 2026 18:54
dmadisetti and others added 2 commits September 24, 2026 11:54
Every persistent cache hit or save now records its entry in a manifest
beside the cache, grouped under a path hash: a static digest of the
defining cell and every cell it depends on, recomputable from source
alone (exposed as BlockHasher.path_hash and hash_cell_closure). Records
accumulate across runs and travel through the Store interface under a
reserved key, `<notebook-stem>-<path-digest>.json`, so remote and
tiered stores carry manifests too, each listing only the entries stored
beside it. Flushes coalesce to one write per store per run (ON_FINISH
hook, teardown retry); a store that declines the write counts as a
failed flush; a save records its entry again even when the session saw
the key before, since a clean can have removed it in between; recording
never fails the user's cell. FileStore.put is now atomic (tmp sibling +
os.replace) and interrupted-write leftovers are excluded from entry
counts and swept by clear().

Also fixes unmangle_local for hyphenated cell ids (a UUID, or a nested
app's prefix): a mangled _private name was previously left mangled in
co_names, so kernel and load_app digests disagreed for such sessions.
Signed-off-by: Dylan Madisetti <dylan@madisetti.me>
@dmadisetti
dmadisetti merged commit a0335e7 into main Sep 24, 2026
19 of 21 checks passed
@dmadisetti
dmadisetti deleted the dm/cache-manifest branch September 24, 2026 18:55
@dmadisetti dmadisetti removed documentation Improvements or additions to documentation bash-focus Area to focus on during release bug bash labels Sep 24, 2026

This branch was successfully deployed

1 active deployment
Preview — b07a6f7f Deployed Sep 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants