Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Speculative Actions: cache priming via adapted subactions - #2109

Draft
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions
Draft

Speculative Actions: cache priming via adapted subactions#2109
sstriker wants to merge 15 commits into
masterfrom
striker/speculative-actions

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

Closes#2083

Summary

Speculative actions speed up rebuilds by pre-populating the action cache with adapted versions of previously recorded build actions. When a dependency changes, compile and link commands from the previous build are adapted with updated input digests and executed ahead of the actual build, so recc gets action cache hits instead of executing from scratch.

  • Recording: subactions captured via recc through remote-apis-socket during sandbox execution
  • Generation: after each build, overlays describe how each subaction's inputs relate to source elements and dependency artifacts
  • Storage: speculative actions stored on artifact proto under the weak cache key (stable across dependency version changes)
  • Priming: before each rebuild, stored actions are instantiated with current dependency digests and submitted fire-and-forget
  • Tiered modes: none/prime-only/source-artifact/intra-element/full — lets users control cost vs benefit

See doc/source/arch_speculative_actions.rst for the full architecture documentation including example scenarios.

Dependencies

Test plan

  • 46 unit tests (generator, instantiator, pipeline integration, weak key, modes)
  • 5 integration tests with real sandbox + recc + buildbox-casd
  • Full test suite: 1753 passed, 0 failed
  • End-to-end with freedesktop-sdk sstriker/recc

🤖 Generated with Claude Code

sstrikerand others added 15 commits March 23, 2026 01:10
Proto definitions:
- speculative_actions.proto: SpeculativeActions, SpeculativeAction,
Overlay messages for storing build action overlays
- ActionResult.subactions (field 99): repeated Digest for nested
execution action digests recorded by recc/trexe
- Artifact.speculative_actions: Digest field for SA proto storage
Generator (generator.py):
- Analyzes completed builds to extract subaction digests from Actions
- Builds digest cache mapping file hashes to source elements
(SOURCE priority > ARTIFACT priority)
- Creates overlays linking each input file to its origin element/path
- Generates artifact_overlays for downstream dependency tracing
Instantiator (instantiator.py):
- Fetches base actions from CAS and resolves SOURCE/ARTIFACT overlays
- Replaces file digests in action input trees recursively
- Stores adapted actions back to CAS
- Optimization: skips overlays for unchanged dependencies
Config:
- scheduler.speculative-actions flag (default false) in userconfig.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synchronize the Remote Execution API proto with the version in
buildbox. This adds the subactions field to ActionResult (field 99)
for tracking nested executions (e.g. compiler invocations via recc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scheduler queues:
- SpeculativeActionGenerationQueue: runs after BuildQueue, extracts
subaction digests, generates overlays, stores SA by weak key
- SpeculativeCachePrimingQueue: runs after PullQueue, retrieves stored
SA, instantiates with current dep digests, submits Execute to
buildbox-casd's local execution scheduler for verified caching
Pipeline wiring:
- _stream.py: conditionally adds queues when speculative-actions enabled
- _context.py: reads speculative-actions scheduler config flag
- element.py: _get_weak_cache_key(), subaction digest storage,
_assemble() transfers digests from sandbox after build
- _artifactcache.py: store/get_speculative_actions() with weak key path
- _cas/cascache.py: fetch_proto()/store_proto() for SA serialization
- sandbox.py: accumulates subaction digests across sandbox.run() calls
- _sandboxreapi.py: reads action_result.subactions after execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
32 tests covering the full speculative actions pipeline without sandbox:
Weak key (test_weak_key.py, 13 tests):
- Stability: same inputs, dep version changes, dependency ordering
- Invalidation: source, command, env, sandbox, plugin, dep changes
Generator (test_generator_unit.py, 6 tests):
- SOURCE/ARTIFACT overlay production, priority, unknown digests
Instantiator (test_instantiator_unit.py, 6 tests):
- Digest replacement, nested dirs, multiple overlays, artifact overlays
Pipeline integration (test_pipeline_integration.py, 7 tests):
- Generate -> store -> retrieve -> instantiate roundtrips
- Priming scenario: dep changes, adapted actions have updated digests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration tests using recc remote execution through remote-apis-socket
(requires --integration and buildbox-run):
- test_speculative_actions_generation: autotools build with CC=recc gcc,
verifies remote execution and generation queue processed elements
- test_speculative_actions_dependency_chain: 3-element chain build
- test_speculative_actions_rebuild_with_source_change: patches amhello
source, rebuilds, verifies new artifact and generation on rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ACTION overlay type to track inter-subaction output dependencies.
When a compile subaction produces main.o and the link subaction consumes
it, an ACTION overlay records this relationship so that priming can
chain the adapted output through to the link action's input tree.
Proto: ACTION = 2 in OverlayType, new source_action_digest field (3)
to identify the producing subaction by its base action digest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lution
Generator: accepts ac_service and artifactcache. Processes subactions in
order, fetching ActionResults to track outputs. Generates ACTION overlays
for intra-element (compile→link) and cross-element (dependency subaction
outputs) dependencies. Indexes dependency sources alongside artifacts so
that both SOURCE and ARTIFACT overlays are generated for the same file
digest, enabling fallback resolution (SOURCE > ARTIFACT > ACTION).
Instantiator: accepts ac_service. Resolves overlays with fallback — once
a target digest is resolved by a higher-priority overlay, lower-priority
overlays for the same target are skipped. Cross-element ACTION overlays
fall back to action cache lookup when source_element is set.
Generation queue: passes ac_service and artifactcache to generator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p callbacks
Rearchitect the priming queue to run concurrently with building by
using the PENDING state pattern. Elements with stored SpeculativeActions
but unbuilt dependencies enter the priming queue as PENDING instead of
READY, holding them while background priming runs in the scheduler's
thread pool.
Element: new _set_build_dep_cached_callback fires each time a build
dependency becomes cached (unlike _set_buildable_callback which fires
only when ALL deps are cached). Enables incremental overlay resolution
as dependencies complete one by one.
Priming queue lifecycle:
- PENDING: background priming fires immediately via run_in_executor,
submitting independent subactions fire-and-forget
- Per-dep callback: as each dep completes, re-attempts overlay
resolution for newly available ARTIFACT/ACTION overlays
- READY (buildable): final pass resolves remaining ACTION overlays
(producing subactions now in AC), submits remaining
- Done: element proceeds to BuildQueue with all actions primed
Unchanged actions (instantiated digest equals base digest) skip
submission — already in the action cache from the previous build.
The Execute submission reads the first stream response to confirm
acceptance by casd, then drops the stream. The action executes
asynchronously and its result appears in the action cache.
Architecture docs updated for the concurrent priming design, overlay
fallback resolution, and data availability considerations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…solution
Replace per-element adapted_digests/action_outputs state with a global
_instantiated_actions dict (base_action_hash -> adapted_action_digest)
shared across all elements during priming. This fixes cross-element
ACTION overlay resolution when dependency elements are adapted but not
rebuilt (e.g. intermediate files like generated headers or .o files
produced by a dependency's subactions).
Key changes:
- Generator: fix overlay sort order to SOURCE > ACTION > ARTIFACT.
ACTION overlays resolve intermediate files (.o, generated headers)
not present in artifacts, so they should be tried before ARTIFACT.
- Instantiator: replace action_outputs parameter with global
instantiated_actions dict. Add step-0 already-instantiated check.
Add resolved_cache parameter to avoid re-resolving overlays across
passes when an SA is deferred.
- Priming queue: global _instantiated_actions dict and _primed_elements
set as class-level shared state. Unresolvable ACTION overlays are
removed from the in-memory SA proto when their source_element has
finished priming. Cache deserialized spec_actions proto on the element
so mutations and resolution caches survive across passes. Add
dep-primed callback to trigger incremental priming when a dependency
finishes priming (earlier than dep-cached).
- Element: add _set_build_dep_primed_callback and
_notify_build_deps_primed for the dep-primed notification mechanism.
- Architecture doc: add 6 example scenarios, expand ReferencedSAs
future optimization, describe global instantiated_actions approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the boolean speculative-actions config with tiered modes
that let users control the cost/benefit trade-off:
- none: disabled entirely
- prime-only: use existing Speculative Actions to prime, don't
generate new ones
- source-artifact: generate SOURCE and ARTIFACT overlays (no AC calls)
- intra-element: also generate intra-element ACTION overlays
- full: also generate cross-element ACTION overlays
Each mode includes all capabilities of the previous modes. Boolean
values (True/False) are accepted for backward compatibility.
The mode gates which queues are enabled (priming for all except none,
generation for source-artifact and above) and which overlay types the
generator produces (ACTION overlay logic skipped in source-artifact
mode, cross-element seeding skipped in intra-element mode).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two optimizations to reduce generation overhead:
1. Return input_digests from _generate_action_overlays() so the caller
can reuse them for ACTION overlay matching, instead of re-fetching
the Action and re-extracting digests (eliminated duplicate CAS reads
per subaction).
2. Collect artifact file entries during _build_digest_cache traversal
(_own_artifact_entries) and reuse in _generate_artifact_overlays(),
eliminating a redundant full traversal of the element's artifact tree.
Cross-element ACTION overlays use eager dependency output seeding
because they enable earlier resolution than ARTIFACT overlays (the
dep's adapted actions are available via instantiated_actions before
the dep's full build completes). The docstring on
_seed_dependency_outputs explains this trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an in-memory cache for parsed Directory protos to avoid redundant
CAS reads during overlay resolution and tree modification.
Many overlays reference files in the same directory trees — the same
intermediate Directory protos were being read from disk repeatedly.
The cache is keyed by digest hash and shared across all subactions
within a single instantiator instance (~1MB for ~10K directories).
Used in both _find_file_by_path() (overlay resolution) and
_replace_digests_in_tree() (action adaptation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-element ActionResult cache to avoid redundant GetActionResult
gRPC calls when checking ACTION overlay resolvability across
background, incremental, and final priming passes.
Once an ActionResult is found for an adapted action digest, it is
cached and reused on subsequent passes. Negative results (action
submitted but not yet complete) are NOT cached since the result may
become available on the next pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Optimize _prefetch_cas_blobs to reduce remote FetchTree latency:
1. Deduplicate input root digests — many subactions share the same
input trees, so redundant FetchTree calls are eliminated.
2. Issue FetchTree calls concurrently via ThreadPoolExecutor (up to
16 workers) instead of sequentially.
Individual FetchTree calls are preserved (no synthetic root) to
maintain remote cache hit rates — input roots from actual builds
are likely already cached on the remote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6 tests verifying each mode produces the correct overlay types
and makes the expected number of AC calls:
- source-artifact mode: no ACTION overlays, zero AC calls
- intra-element mode: ACTION overlays for within-element chains only,
AC calls limited to own subactions (no dep seeding)
- full mode: cross-element ACTION overlays from dep subactions
- backward-compat: enum values exist and are distinct
- AC call counting: verifies source-artifact makes 0 calls,
intra-element makes exactly N calls (one per subaction)
Also updated FakeArtifactCache to support lookup by artifact identity
(used by _seed_dependency_outputs for cross-element ACTION overlays).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

Speculative Actions: Predictive Cache Priming for BuildStream

1 participant

@sstriker