lc_agents/approval.rs
1// lc-agents/src/approval.rs
2//! Approval gate (§4.2): **asynchronous** approval gate before tool execution.
3//!
4//! Coexists with the synchronous hook system (`hooks::ApprovalHook` /
5//! `ToolCallAction`) without conflict: the sync hook runs inside
6//! `execute_tool`, and the approval gate also runs inside `execute_tool`, after
7//! the sync hook and **before** actual execution — the order is
8//! `budget gate → execute_tool (sync hook → approval gate → tool execution)`.
9//!
10//! The framework only provides the gate; the approval strategy is implemented
11//! by the caller via [`ApprovalHandler`]. [`AllowAll`] is a reference
12//! implementation for testing / demos.
13
14use async_trait::async_trait;
15use serde_json::Value;
16
17use crate::hooks::ToolCallContext;
18
19/// Approval decision before tool execution.
20#[derive(Debug, Clone)]
21pub enum ApprovalDecision {
22 /// Allow: execute as-is.
23 Allow,
24 /// Deny: do not execute the tool; feed the reason back to the loop as an
25 /// observation so the next round replans.
26 Deny {
27 /// Denial reason (goes into the observation).
28 reason: String,
29 },
30 /// Modify arguments then execute: replaces the original arguments with `arguments`.
31 Modify {
32 /// Replacement tool arguments.
33 arguments: Value,
34 /// Modification note (for logging).
35 note: String,
36 },
37}
38
39/// Approval-gate interface. Implemented by the caller and injected via
40/// `AgentExecutor::with_approval`.
41///
42/// `approve` is async: implementations may `await` an approval signal (CLI
43/// interaction / webhook / messaging channel). Same-process resume works
44/// naturally through async/await — the future suspends waiting for the signal
45/// and continues from the same line when it arrives, no serialization /
46/// Checkpointer needed.
47#[async_trait]
48pub trait ApprovalHandler: Send + Sync {
49 /// Called before tool execution; returns the approval decision.
50 async fn approve(&self, ctx: &ToolCallContext) -> ApprovalDecision;
51}
52
53/// Reference implementation: allows everything. For tests / demos.
54#[derive(Debug, Default)]
55pub struct AllowAll;
56
57#[async_trait]
58impl ApprovalHandler for AllowAll {
59 async fn approve(&self, _ctx: &ToolCallContext) -> ApprovalDecision {
60 ApprovalDecision::Allow
61 }
62}