chat-rs
Tools

Human in the loop

Pause the chat loop so a person can approve, reject, or edit a tool call before it runs.

Some tool calls should not run on their own. Deleting files, sending email, or moving money is the kind of thing you may want a person to sign off on first. chat-rs supports this by letting a tool's strategy return Action::RequireApproval, which pauses the loop instead of executing the call.

When that happens, complete returns ChatOutcome::Paused { reason } rather than Complete. You resolve the pending tools, then call resume to pick the same turn back up.

How a pause is reported

PauseReason tells you which tools need attention:

pub enum PauseReason {
    AwaitingApproval { tool_ids: Vec<CallId> },
    Scheduled { tool_ids: Vec<CallId>, earliest: std::time::SystemTime },
    Mixed { approvals: Vec<CallId>, scheduled: Vec<(CallId, SystemTime)> },
}

For human approval you care about AwaitingApproval. Each id points at a tool sitting in the Messages you passed in, waiting for a decision.

Resolve a pending tool

Look the tool up with Messages::find_tool_mut, then call either approve or reject on it. approve(None) runs the call as the model asked; pass Some(call) to run an edited version instead. reject(Some(reason)) declines it and lets the model see why.

fn resolve_pending(reason: &PauseReason, messages: &mut Messages) {
    if let PauseReason::AwaitingApproval { tool_ids } = reason {
        for id in tool_ids {
            let Some(tool) = messages.find_tool_mut(id) else { continue };
            match ask_user(&tool.call.name, &tool.call.arguments) {
                Decision::Approve => tool.approve(None),
                Decision::Reject(why) => tool.reject(Some(why)),
            }
        }
    }
}

The complete then resume loop

Run complete. If it comes back Paused, resolve the listed tools and call resume with the same Messages. Keep going until you get Complete, since one turn can pause more than once.

use chat_rs::{ChatOutcome, Messages, PauseReason};

let mut messages = Messages::default();
messages.push(content::from_user(parts!["Delete the old logs."]));

loop {
    match chat.complete(&mut messages).await.map_err(|e| e.err)? {
        ChatOutcome::Complete(res) => {
            println!("{:?}", res.content.parts.last());
            break;
        }
        ChatOutcome::Paused { reason } => {
            resolve_pending(&reason, &mut messages);
            if let ChatOutcome::Complete(res) =
                chat.resume(&mut messages).await.map_err(|e| e.err)?
            {
                println!("{:?}", res.content.parts.last());
                break;
            }
        }
    }
}

The decision about which tools require approval lives in the tool's strategy. See Tools with context for how metadata on each tool feeds that strategy.

On this page