Caching v0.3: robust trajectory caching, precise parameters & observability - #311
Merged
Merged
Conversation
… format to 0.3 Fixes two reported trajectory-caching bugs and several logic bugs surfaced by an adversarial review of the caching subsystem. Reported bugs: - Resuming after the last (non-cacheable) step crashed with "Invalid start_from_step_index". `start_from_step_index == len(trajectory)` now means "already complete" and flows into the COMPLETED/verification path; only truly out-of-range indices raise. Empty/all-non-cacheable trajectories are handled too. The non-cacheable pause message now tells the agent the exact resume index, or that the step was the final one (verify instead of resume). - Replaced the "list available trajectories" workflow with automatic detection: in execute/auto mode the SDK looks up <cache_dir>/<filename> and, if a usable trajectory exists, injects its details (path + parameters) into the first user message so the agent can switch to the CacheExecutor immediately. In auto mode with no usable trajectory the agent is told none exists and the run is recorded. Removed retrieve_available_trajectories_tool and updated CACHE_USE_PROMPT. Cache format version bumped 0.2 -> 0.3. Additional bugs fixed (found during review): - CachingSettings.filename was silently ignored (no such field); added it as a top-level field used for both lookup and recording. - execute-only mode passed cache_manager=None, causing CacheExecutor to raise RuntimeError; a CacheManager is now always created when executing. - verify_cache_execution(success=False) now actually invalidates the cache, and success=True records the completion (execution_attempts/last_executed_at). - CACHE_USE prompt and CacheExecutor speaker no longer leak across act() calls; per-call caching tools no longer accumulate on the persistent tool collection (which could persist a later run's result to a previous run's trajectory). - LLM-identified parameters with invalid names or empty values are dropped (empty values previously corrupted every string in the trajectory). - Recording no longer writes/overwrites a cache with no cacheable steps. - finish_recording errors in teardown are logged instead of masking the run result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ervability
Two improvements to the trajectory-caching mechanism.
1) Parameter identification is far more precise (fewer false positives):
- Only user-entered free text is eligible. Coordinates, key names,
action/enum values, counts and tool names are filtered out before the LLM
is asked, so they can no longer be mis-identified as parameters. When a
recording has no free-text values, the LLM is not called at all.
- The system prompt is rewritten to be conservative and precision-first:
parameterize a value only if replaying the recorded literal would clearly
be wrong on a later run; when in doubt, keep the literal. Returning no
parameters is explicitly normal.
- Identified values are validated against the offered candidates, so
hallucinated/reformatted values are dropped.
2) Caching now explains what and why, via logs AND the reporter (source
"Cache"), so the information also lands in the HTML report, not only stderr:
- cache hit/miss, replay start, pause on a non-cacheable step (with the tool
name), completion, verification outcome, invalidation, and recording.
- Verification failures now include the agent's reason and the affected cache
file instead of an unexplained "Cache verification failed!".
Stacked on the trajectory-caching-improvements branch (PR #310).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`CachingSettings.filename` may include subdirectories, resolved relative to `cache_dir`, so a test at tests/mytests_1/test_something.py can use filename="mytests_1/test_something" and map to .askui_cache/mytests_1/test_something.json. Lookup already preserved subdirectories, but recording crashed with FileNotFoundError because the nested parent directory was never created. Create parents on record (start_recording, _generate_cache_file) and on metadata writes (_write_cache_file), so the save location matches the lookup location. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Added nested cache filename support (commit 9f71337): |
…tly ignored)
The replay delay lives on CacheExecutionSettings.delay_time_between_actions and
is applied correctly by the CacheExecutor between replayed actions. However,
setting it the intuitive way - CachingSettings(delay_time_between_actions=3.0) -
was silently ignored (pydantic extra="ignore"), so the default 1.0s was used and
the delay "did not work".
Add model_config = ConfigDict(extra="forbid") to CachingSettings,
CacheExecutionSettings and CacheWritingSettings so a misplaced/misspelled field
raises a clear ValidationError (and is caught by mypy) instead of being dropped.
The correct nested form still works:
CachingSettings(execution_settings=CacheExecutionSettings(
delay_time_between_actions=3.0)).
Docs updated to call out where the delay lives.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cache invalidation logged the full (often long) reason at WARNING. Keep a
concise WARNING headline ("Cache invalidated and will not be reused.") and log
the full reason at INFO instead. report_cache_event() gains an optional `detail`
argument (logged at INFO, appended to the reporter message) to support this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Guard nested cache filenames so they stay within cache_dir: reject absolute
paths and ".." components (ensure_relative_cache_filename) at both lookup and
recording. Previously an absolute/traversing filename could escape cache_dir
and auto-create arbitrary directories.
- Narrow the parameter-candidate denylist to purely structural keys: drop "id",
"name" and "amount" so genuinely dynamic user-entered values (reference/order
ids, usernames, typed amounts) can still be parameterized; the conservative
prompt decides.
- Avoid a literal "None" in the cache-replay-failure message when there is no
error message ("unknown error" instead).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
philipph-askui
changed the base branch from
fix/trajectory-caching-improvements
to
main
August 27, 2026 20:25
philipph-askui
marked this pull request as draft
August 27, 2026 20:25
philipph-askui
marked this pull request as ready for review
August 27, 2026 20:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Caching v0.3
A single PR covering the caching v0.3 work: it makes trajectory caching correct, precise, observable, and hard to misconfigure, and bumps the cache format to
0.3.Reliability & mechanism
execute/automode the SDK looks up<cache_dir>/<filename>and, if a usable trajectory exists, injects its details (path + required parameters) into the first user message so the agent hands off to theCacheExecutorimmediately. The old "list available trajectories" tool and prompt workflow are removed.CachingSettings.filenamenow actually works and may include subdirectories relative tocache_dir(e.g.filename="mytests_1/test_something"→.askui_cache/mytests_1/test_something.json) for both lookup and recording — with a guard so a filename can't escapecache_dir. Previously it was silently ignored, and nested recording crashed.start_from_step_index == len(trajectory)means "already complete"; the pause message tells the agent the exact resume index or that it was the final step.execute-only mode no longer crashes (aCacheManageris always created when executing).verify_cache_executionnow persists outcomes: success records the attempt; failure invalidates the cache.CACHE_USEprompt, theCacheExecutorspeaker, and per-call caching tools no longer bleed into lateract()calls.Precise parameter identification
Observability (logs and reporter)
Cache, so it also shows up in the HTML report): cache hit/miss, replay start, pause on a non-cacheable step (with tool name), completion, verification outcome with the agent's reason and the cache file, invalidation, and recording written/skipped.Safer configuration
CachingSettings,CacheExecutionSettings, andCacheWritingSettingsreject unknown fields (extra="forbid"), so a misplaced option — e.g.CachingSettings(delay_time_between_actions=3.0)instead of nesting underexecution_settings— raises a clear error instead of being silently ignored.Testing & docs
pdm run qa:fixclean (typecheck + format + lint); full unit suite green (784 passed), with dedicated tests for auto-detection, resume-at-end, parameter precision/validation, nested filenames + escape guard,extra="forbid", and the observability messages.docs/06_caching.mdrewritten for thefilename-based flow with Dynamic Parameters and Observability sections.Reviews
Developed with three adversarial code-review passes (recording path, replay/orchestration path, and a final review of the parameter/observability changes); all substantiated findings were fixed.
Known follow-ups (intentionally out of scope)
str()limitation).🤖 Generated with Claude Code