Use serving assessments for adaptive Guardian review (#58075)

## What changed

- Match final serving assessments to their response and output item, then retain them with tool invocations and code cells across waits.
- Use matching assessments instead of asynchronous scoring for adaptive review: low-risk actions can skip synchronous review, while elevated-risk actions require it.
- Revalidate captured authorization and policy before approval. Require fresh review for stale or incomplete authorization, preserve explicit review requirements, and discard assessments when hooks rewrite tool input.
- Keep asynchronous scoring as the fallback when an assessment is missing or does not match.

## Testing

Add integration coverage for direct and nested tool calls, hook rewrites, explicit escalation, interrupted responses, catalog-driven policy, incomplete authorization, and assessment retention across code-cell yields and waits.

GitOrigin-RevId: 4346a227204137f018c03c34b302d6f1e9592c96
This commit is contained in:
jif
2026-10-29 12:54:39 +00:00
committed by copyberry
parent 6b3b3349f3
commit 9def09afa7
26 changed files with 592 additions and 97 deletions
+5
View File
@@ -41,6 +41,11 @@ pub(crate) async fn notify_tool_start(
for contributor in contributors {
contributor
.on_tool_start(ToolStartInput {
serving_assessment: invocation
.step_context
.serving_assessment
.as_ref()
.map(|value| value.requires_review),
session_store: &invocation.session.services.session_extension_data,
thread_store,
turn_store: invocation.turn.extension_data.as_ref(),
+1 -1
View File
@@ -49,7 +49,7 @@ struct ToolCallTimingGuard {
pub(crate) struct ToolCallRuntime {
session: Arc<Session>,
// Tool calls may run later, so retain the step whose tool list advertised them.
step_context: Arc<StepContext>,
pub(crate) step_context: Arc<StepContext>,
tracker: SharedTurnDiffTracker,
parallel_execution: Arc<RwLock<()>>,
timing: Option<DirectToolTiming>,
+15 -1
View File
@@ -887,7 +887,11 @@ impl ToolRegistry {
PreToolUseHookResult::Continue {
updated_input: Some(updated_input),
} => match tool.with_updated_hook_input(invocation.clone(), updated_input) {
Ok(updated_invocation) => {
Ok(mut updated_invocation) => {
if updated_invocation.payload != invocation.payload {
Arc::make_mut(&mut updated_invocation.step_context)
.serving_assessment = None;
}
invocation = updated_invocation;
}
Err(err) => return Err(fail_before_execution(&invocation, err).await),
@@ -898,6 +902,16 @@ impl ToolRegistry {
}
}
if let Some(assessment) = &invocation.step_context.serving_assessment {
let mut assessment = assessment.as_ref().clone();
match crate::guardian::tool_permission_context(&invocation) {
Ok(permissions) => assessment.authorization.permissions = permissions,
Err(_) => assessment.requires_review = true,
}
Arc::make_mut(&mut invocation.step_context).serving_assessment =
Some(Arc::new(assessment));
}
if registered_tool.native_review == NativeReview::Required
&& let Err(err) = super::review_native_tool_call(&invocation).await
{