Useful Pluginstool_approval
DocsUseful Pluginstool_approval

tool_approval

Web-based approval for dangerous tools via WebSocket

Quick Start

main.py
from connectonion import Agent, bash from connectonion.useful_plugins import tool_approval agent = Agent("assistant", tools=[bash], plugins=[tool_approval]) agent.io = my_websocket_io # Required for web mode agent.input("Install dependencies") # → Client receives: {"type": "approval_needed", "tool": "bash", "arguments": {"command": "npm install"}} # → Client responds: {"approved": true, "scope": "session"} # ✓ bash approved (session)

Lifecycle

User sends prompt
    ↓
Agent calls LLM
    ↓
LLM returns tool_calls batch: [bash("npm install"), write("config.json"), bash("npm build")]
    ↓
tool_executor iterates sequentially:
    ↓
┌─ Tool #1: bash("npm install")
│   before_each_tool fires → check_approval()
│   → Is it safe? No (bash is DANGEROUS)
│   → Already approved for session? No
│   → Send to client:
│       {
│         "type": "approval_needed",
│         "tool": "bash",
│         "arguments": {"command": "npm install"},
│         "batch_remaining": [
│           {"tool": "write", "arguments": "{...}"},
│           {"tool": "bash", "arguments": "{\"command\": \"npm build\"}"}
│         ]
│       }
│   → BLOCK — wait for client response
│   ↓
│   Client responds: {"approved": true, "scope": "session"}
│   → Execute bash("npm install")
│   → Save "bash" as session-approved
│
├─ Tool #2: write("config.json")
│   before_each_tool fires → check_approval()
│   → Is it safe? No (write is DANGEROUS)
│   → Send approval_needed (batch_remaining: [bash(...)])
│   → Client responds: {"approved": false, "mode": "reject_soft"}
│   → Skip this tool, continue to next
│
├─ Tool #3: bash("npm build")
│   before_each_tool fires → check_approval()
│   → bash is session-approved → skip approval, execute immediately
│
└─ Done. Return results to LLM.

Rejection Modes

When the client rejects a tool, the mode field determines what happens next:

reject_soft (Skip)

Skip this tool, agent loop continues. The LLM receives a hint to ask the user what they prefer.

code
{"approved": false, "mode": "reject_soft", "feedback": "Don't write that file"}
  • • Current tool is skipped (raises ValueError)
  • • Next tool in the batch proceeds normally
  • • LLM gets: "User rejected tool 'write'. Feedback: Don't write that file\n\n[System reminder: Ask the user...]"

reject_hard (Stop)

Skip this tool AND all remaining tools in the batch. The agent loop stops and waits for new user input.

code
{"approved": false, "mode": "reject_hard", "feedback": "Wrong approach entirely"}
  • • Current tool is skipped (raises ValueError)
  • stop_signal flag is set in session
  • • All remaining tools in the batch are auto-rejected
  • • Agent loop stops — LLM does NOT get another turn
  • • User must send a new message to continue
Default mode when mode is not provided: reject_hard

Tool Classification

Safe Tools (No Approval)

Read-only operations that never modify state:

read, read_file, glob, grep, search
list_files, get_file_info, task, load_guide
enter_plan_mode, exit_plan_mode, write_plan
task_output, ask_user

Dangerous Tools (Require Approval)

Operations that can modify files or have side effects:

bash, shell, run, run_in_dir
write, edit, multi_edit
run_background, kill_task
send_email, post, delete, remove

Unknown Tools

Tools not in either list are treated as safe (no approval needed).

Config-Based Auto-Approval

Auto-approve safe commands permanently via host.yaml configuration. Config permissions never expire and apply to all sessions.

Configuration Example

code
# .co/host.yaml permissions: # Simple tool name - matches any call "read_file": allowed: true source: config reason: safe read operation expires: type: never # Exact bash command "Bash(git status)": allowed: true source: config reason: safe git read expires: type: never # Wildcard - matches command prefix "Bash(git diff *)": allowed: true source: config reason: safe git diff expires: type: never # Parameter matching - file pattern "write": allowed: true source: config reason: safe doc edits when: file_path: "*.md" expires: type: never

Pattern Types

Simple Tool Name

Matches any call to the tool

"read_file"

Exact Bash Command

Only matches exact command

"Bash(git status)"

Wildcard Bash Command

Matches command prefix

"Bash(git diff *)"

Parameter Matching

Uses 'when' field for granular control

when: {file_path: "*.md"}

Priority Order

  1. 1.
    Safe tools - Always approved (SAFE_TOOLS list)
  2. 2.
    Config permissions - Loaded from host.yaml (source: config)
  3. 3.
    Skill permissions - Temporary, turn-scoped (source: skill)
  4. 4.
    Session approvals - User approved for session (source: user)
  5. 5.
    Runtime approval - Ask user (if none of above match)

Bash Command Chain Permissions

Uses bashlex to parse and validate command chains - ALL commands must be permitted.

All Permitted

code
# Config: permissions: "Bash(pwd)": {allowed: true} "Bash(ls *)": {allowed: true} # Command: pwd && ls -F # Result: Auto-approved ⚡

Partial Permission

code
# Config: permissions: "Bash(pwd)": {allowed: true} # rm is NOT whitelisted # Command: pwd && rm -rf / # Result: Requires approval ⚠️

Supported Syntax

SyntaxExampleCommands Extracted
&&pwd && ls["pwd", "ls"]
||test -f file || echo no["test", "echo"]
|cat file | grep test["cat", "grep"]
;echo a; echo b["echo", "echo"]

Security

Whitelist-first: One dangerous command = whole chain rejected.

# ❌ REJECTED even though pwd is safe
pwd && rm -rf /

Client Protocol

Server sends

code
{ "type": "approval_needed", "tool": "bash", "arguments": {"command": "npm install"}, "batch_remaining": [{"tool": "write", "arguments": "..."}] }

Client responds

code
{"approved": true, "scope": "once"}
code
{"approved": true, "scope": "session"}
code
{"approved": false, "mode": "reject_soft", "feedback": "Use yarn instead"}
code
{"approved": false, "mode": "reject_hard", "feedback": "Wrong approach"}

Approval Scopes

ScopeBehavior
onceApprove this call only
sessionApprove for rest of session (stored in memory)

See Also

Star us on GitHub

If ConnectOnion saves you time, a ⭐ goes a long way — and earns you a coffee chat with our founder.