Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3f67405
feat(response-cache): add tool-result caching
zhongxuanwang-nv Jul 31, 2026
8d7dec7
test(response-cache): cover cache failure paths
zhongxuanwang-nv Jul 31, 2026
f42971d
fix(response-cache): harden tool result caching
zhongxuanwang-nv Jul 31, 2026
2c5789d
fix(response-cache): harden tool cache safety
zhongxuanwang-nv Jul 31, 2026
cd92e5d
chore: keep tool cache docs in documentation PR
zhongxuanwang-nv Aug 3, 2026
3f9e2a9
fix: use v1 response cache version identifiers
zhongxuanwang-nv Aug 3, 2026
deb61a5
fix(response-cache): address review feedback
zhongxuanwang-nv Aug 8, 2026
7c13df4
test(cli): use live doctor probe helper
zhongxuanwang-nv Aug 12, 2026
fa88208
fix(response-cache): reject broad tool caching
zhongxuanwang-nv Aug 12, 2026
635ee09
test(response-cache): tighten tool cache coverage
zhongxuanwang-nv Aug 12, 2026
0f2281d
fix(cli): report tool cache configuration offline
zhongxuanwang-nv Aug 12, 2026
8c200ea
test(response-cache): isolate cache surface keys
zhongxuanwang-nv Aug 12, 2026
428b8b0
test(response-cache): inspect mark metadata object
zhongxuanwang-nv Aug 12, 2026
9fe18ed
test(cli): configure tool cache diagnostics
zhongxuanwang-nv Aug 12, 2026
1ccf18c
ci: use setup Python for Rust and Node jobs
zhongxuanwang-nv Aug 12, 2026
c658c2d
ci: use setup Python for Python jobs
zhongxuanwang-nv Aug 12, 2026
d02ce64
Merge remote-tracking branch 'origin/main' into feat/response-cache-s…
zhongxuanwang-nv Aug 12, 2026
ce9cad1
revert: restore CI workflows from main
zhongxuanwang-nv Aug 12, 2026
30e010f
Merge remote-tracking branch 'origin/main' into feat/response-cache-s…
zhongxuanwang-nv Aug 12, 2026
5980c83
refactor(response-cache): type cache decision labels
zhongxuanwang-nv Aug 12, 2026
eea1bb7
fix(ci): avoid manual uv Python downloads
zhongxuanwang-nv Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,20 @@ jobs:
python-version: ${{ steps.ci-config.outputs.default_python_version }}
architecture: arm64

- name: Set up Python
if: ${{ matrix.platform != 'windows-arm64' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
if: ${{ matrix.platform == 'windows-arm64' }}
run: |
set -e
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install managed Python
if: ${{ matrix.platform != 'windows-arm64' }}
run: |
set -e
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- name: Install Windows ARM64 OpenSSL
if: ${{ matrix.platform == 'windows-arm64' }}
shell: pwsh
Expand Down Expand Up @@ -243,21 +242,21 @@ jobs:
python-version: ${{ steps.ci-config.outputs.default_python_version }}
architecture: arm64

- name: Set up Python
if: ${{ !contains(matrix.platform, 'musl') && matrix.platform != 'windows-arm64' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
if: ${{ matrix.platform == 'windows-arm64' }}
if: ${{ !contains(matrix.platform, 'musl') }}
run: |
set -e
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install managed Python
if: ${{ !contains(matrix.platform, 'musl') && matrix.platform != 'windows-arm64' }}
run: |
set -e
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- uses: taiki-e/install-action@c070f87102a1c75b3183910f391c1cb887fe13c8 # v2.77.6
if: ${{ !contains(matrix.platform, 'musl') }}
with:
Expand Down Expand Up @@ -487,19 +486,21 @@ jobs:
python-version: ${{ steps.ci-config.outputs.default_python_version }}
architecture: arm64

- name: Set up Python
if: ${{ !contains(matrix.platform, 'musl') && matrix.platform != 'windows-arm64' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
if: ${{ matrix.platform == 'windows-arm64' }}
if: ${{ !contains(matrix.platform, 'musl') }}
run: |
set -e
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install managed Python
if: ${{ !contains(matrix.platform, 'musl') && matrix.platform != 'windows-arm64' }}
run: UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- name: Download matching Python wheel artifact
if: ${{ !contains(matrix.platform, 'musl') && matrix.platform != 'source-and-plugin' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand Down
25 changes: 13 additions & 12 deletions .github/workflows/ci_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,20 @@ jobs:
python-version: ${{ steps.ci-config.outputs.default_python_version }}
architecture: arm64

- name: Set up Python
if: ${{ matrix.platform != 'windows-arm64' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
if: ${{ matrix.platform == 'windows-arm64' }}
run: |
set -e
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install managed Python
if: ${{ matrix.platform != 'windows-arm64' }}
run: |
set -e
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- name: Add Python DLL directory to PATH on Windows
if: runner.os == 'Windows'
run: |
Expand Down Expand Up @@ -438,19 +437,21 @@ jobs:
python-version: ${{ steps.ci-config.outputs.default_python_version }}
architecture: arm64

- name: Set up Python
if: ${{ matrix.runtime_image == '' && matrix.platform != 'windows-arm64' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
if: ${{ matrix.platform == 'windows-arm64' }}
if: ${{ matrix.runtime_image == '' }}
run: |
set -e
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install managed Python
if: ${{ matrix.runtime_image == '' && matrix.platform != 'windows-arm64' }}
run: UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- name: Download matching CLI binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down
39 changes: 30 additions & 9 deletions .github/workflows/fern-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,18 @@ jobs:
cache-bin: false
save-if: false

- name: Install managed Python
working-directory: source-checkout
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
run: |
set -euo pipefail
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install just
uses: taiki-e/install-action@c070f87102a1c75b3183910f391c1cb887fe13c8 # v2.77.6
Expand Down Expand Up @@ -287,11 +294,18 @@ jobs:
cache-bin: false
save-if: false

- name: Install managed Python
working-directory: source-checkout
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
run: |
set -euo pipefail
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install just
uses: taiki-e/install-action@c070f87102a1c75b3183910f391c1cb887fe13c8 # v2.77.6
Expand Down Expand Up @@ -442,11 +456,18 @@ jobs:
cache-bin: false
save-if: false

- name: Install managed Python
working-directory: source-checkout
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.ci-config.outputs.default_python_version }}

- name: Select setup-python interpreter for uv
run: |
set -euo pipefail
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}
python - <<'PY' >> "$GITHUB_ENV"
import sys
print(f"UV_PYTHON={sys.executable}")
PY

- name: Install just
uses: taiki-e/install-action@c070f87102a1c75b3183910f391c1cb887fe13c8 # v2.77.6
Expand Down
20 changes: 16 additions & 4 deletions crates/adaptive/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use nemo_relay::plugin::ConfigPolicy;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value as Json};

use crate::response_cache::config::{BackendConfig, KEY_STRATEGY_EXACT_REQUEST};
use crate::response_cache::config::{BackendConfig, KEY_STRATEGY_EXACT_REQUEST, ToolCacheConfig};

/// Canonical config document for the adaptive plugin component.
#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -34,8 +34,8 @@ pub struct AdaptiveConfig {
/// Adaptive Cache Governor settings.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acg: Option<AcgComponentConfig>,
/// Opt-in LLM response cache (exact-match). When present, the
/// adaptive plugin installs the response-cache execution intercept(s).
/// Opt-in exact-match LLM response and tool-result cache. When present,
/// the adaptive plugin installs the response-cache execution intercept(s).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_cache: Option<ResponseCacheConfig>,
/// Adaptive-local unsupported-config policy.
Expand Down Expand Up @@ -191,7 +191,8 @@ impl Default for AcgComponentConfig {
}
}

/// Configuration for the adaptive plugin's `response_cache` feature
/// Configuration for the adaptive plugin's exact-match LLM response and
/// opt-in tool-result cache feature.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ResponseCacheConfig {
Expand All @@ -217,6 +218,9 @@ pub struct ResponseCacheConfig {
pub header_allowlist: Vec<String>,
/// Storage backend selection.
pub backend: BackendConfig,
/// Opt-in tool-result cache configuration.
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolCacheConfig>,
}

impl Default for ResponseCacheConfig {
Expand All @@ -230,6 +234,7 @@ impl Default for ResponseCacheConfig {
key_strategy: KEY_STRATEGY_EXACT_REQUEST.to_string(),
header_allowlist: Vec::new(),
backend: BackendConfig::default(),
tools: None,
}
}
}
Expand Down Expand Up @@ -405,6 +410,13 @@ nemo_relay::editor_config! {
nested: BackendConfig,
default: BackendConfig,
},
tools => {
label: "tools",
kind: Section,
optional: true,
nested: ToolCacheConfig,
default: ToolCacheConfig,
},
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/adaptive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub mod learner;
pub mod plugin_component;
#[cfg(feature = "redis-backend")]
pub mod redis;
/// Opt-in LLM response cache (exact-match).
/// Opt-in exact-match LLM response and tool-result cache.
pub mod response_cache;
mod runtime;
/// Storage backends and backend traits for adaptive state persistence.
Expand All @@ -58,6 +58,7 @@ pub use error::{AdaptiveError, Result};
#[cfg(feature = "redis-backend")]
pub use redis::RedisBackend;
pub use response_cache::RESPONSE_CACHE_MARK;
pub use response_cache::config::{ToolCacheConfig, ToolClass, ToolOverride};
pub use runtime::features::AdaptiveRuntime;
pub use storage::erased::AnyBackend;
pub use storage::memory::InMemoryBackend;
Expand Down
76 changes: 76 additions & 0 deletions crates/adaptive/src/plugin_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ fn validate_response_cache_section(
"key_strategy",
"header_allowlist",
"backend",
"tools",
],
);
if let Some(backend_json) = response_cache_json.get("backend").and_then(Json::as_object) {
Expand All @@ -361,6 +362,9 @@ fn validate_response_cache_section(
);
}
}
if let Some(tools_json) = response_cache_json.get("tools").and_then(Json::as_object) {
validate_response_cache_tools_fields(diagnostics, policy, tools_json);
}
}

fn validate_response_cache_backend_config_fields(
Expand All @@ -383,6 +387,78 @@ fn validate_response_cache_backend_config_fields(
);
}

fn validate_response_cache_tools_fields(
diagnostics: &mut Vec<ConfigDiagnostic>,
policy: &ConfigPolicy,
tools_json: &Map<String, Json>,
) {
const CLASS_FIELDS: &[&str] = &[
"cacheable",
"ttl_seconds",
"bypass_rate",
"arg_skip",
"members",
];
const OVERRIDE_FIELDS: &[&str] = &[
"cacheable",
"ttl_seconds",
"bypass_rate",
"tool_version",
"arg_skip",
];

validate_unknown_fields(
diagnostics,
policy,
Some("response_cache.tools".to_string()),
tools_json,
&[
"enabled",
"priority",
"cache_errors",
"default",
"classes",
"overrides",
],
);

if let Some(default_json) = tools_json.get("default").and_then(Json::as_object) {
validate_unknown_fields(
diagnostics,
policy,
Some("response_cache.tools.default".to_string()),
default_json,
CLASS_FIELDS,
);
}
if let Some(classes_json) = tools_json.get("classes").and_then(Json::as_object) {
for (class_name, class_value) in classes_json {
if let Some(class_object) = class_value.as_object() {
validate_unknown_fields(
diagnostics,
policy,
Some(format!("response_cache.tools.classes.{class_name}")),
class_object,
CLASS_FIELDS,
);
}
}
}
if let Some(overrides_json) = tools_json.get("overrides").and_then(Json::as_object) {
for (tool_name, override_value) in overrides_json {
if let Some(override_object) = override_value.as_object() {
validate_unknown_fields(
diagnostics,
policy,
Some(format!("response_cache.tools.overrides.{tool_name}")),
override_object,
OVERRIDE_FIELDS,
);
}
}
}
}

fn validate_backend_config_fields(
diagnostics: &mut Vec<ConfigDiagnostic>,
policy: &ConfigPolicy,
Expand Down
Loading
Loading