GEMINI LABJP
API — The Interactions API reaches general availability as the primary API for Gemini models and agents, with a stable schemaAGENTS — With GA, the Interactions API formally supports Managed Agents and background execution, with Gemini Omni coming soonLIVE — The Live API and AI Studio add real-time speech-to-speech translation, auto-detecting 70+ languages while preserving intonationOMNI — Gemini Omni Flash, a natively multimodal model, enters API public preview for building custom video workflowsSPARK — Gemini Spark, Google's personal agent, arrives on macOS to work across local files and Google Workspace on your behalfPRO — Gemini 3.5 Pro is reported to be delayed to July 17, centered on a 2M-token context and a Deep Think Reasoning LayerAPI — The Interactions API reaches general availability as the primary API for Gemini models and agents, with a stable schemaAGENTS — With GA, the Interactions API formally supports Managed Agents and background execution, with Gemini Omni coming soonLIVE — The Live API and AI Studio add real-time speech-to-speech translation, auto-detecting 70+ languages while preserving intonationOMNI — Gemini Omni Flash, a natively multimodal model, enters API public preview for building custom video workflowsSPARK — Gemini Spark, Google's personal agent, arrives on macOS to work across local files and Google Workspace on your behalfPRO — Gemini 3.5 Pro is reported to be delayed to July 17, centered on a 2M-token context and a Deep Think Reasoning Layer
Articles/Advanced
Advanced/2026-07-11Advanced

A Risk-Tiered Approval Gate for Gemini Function Calling

Handing full autonomy to an agent is unnerving. This walks through a Gemini function-calling loop that routes tool calls into auto-run and hold-for-approval by risk tier, then feeds the result back to the model after a human signs off.

gemini99function-calling20agent10human-in-the-loop2

Premium Article

One morning I was reading the logs from an overnight batch and stopped mid-scroll. An agent I thought I had scoped to summarization had started reaching toward deleting old files to free up disk space. It never ran — but only because I happened not to register a delete tool. My safety came from an accident, not from a decision.

When you run unattended automation as an indie developer, you meet this "saved by an oversight" moment more than once. The obvious fix — approve every single tool call — throws away the reason you automated in the first place.

Where I settled was the middle. Let harmless reads flow automatically, and gate only the irreversible actions — deletes, charges, outbound messages — behind a human. Rank the tools by how much damage they can do, then treat each rank differently. This article builds that gate into a Gemini function-calling loop, with code you can run.

Why neither "all auto" nor "all approve" holds up

The straightforward function-calling loop is a round trip: the model returns a functionCall, you execute it, you hand back a functionResponse. That directness is exactly what makes it risky when nobody is watching. The model mostly acts in good faith, but one turn of phrase or one unexpected input keeps the odds of it picking a destructive tool above zero.

Gate everything instead, and the human becomes the bottleneck. In my own runs, a 200-item unattended batch produced 612 tool calls in total. Requiring a human on all 612 is not automation.

The leverage is in how many of those 612 are actually dangerous. Once I tagged them by risk, the destructive, irreversible ones came to 18 calls — about 3%. The other 97% were reads and idempotent writes. Show a human the 3%, auto-run the 97%. That split was the practical place where unattended operation and safety could coexist.

TierNatureHandlingExamples
greenRead-only, no side effectsAuto-runList files, search, summarize
yellowIdempotent, recoverable writesPolicy switch (auto by default)Write a report, add a label
redIrreversible, billing, outboundAlways hold for approvalDelete, charge, send email

Tag your tools by risk

Keep three things separate: the tool name, its risk tier, and the actual work it does. Decoupling the tier from the execution logic lets you swap the policy later without touching the handlers.

# risk_policy.py — tool name -> risk tier
# Unknown tools fall through to the most dangerous tier, "red" (fail-safe).
RISK_TIER = {
    "list_files": "green",
    "read_file": "green",
    "search_docs": "green",
    "write_report": "yellow",
    "add_label": "yellow",
    "delete_old_backups": "red",
    "charge_customer": "red",
    "send_email": "red",
}
 
def tier_of(tool_name: str) -> str:
    # An unregistered tool must require approval. Fail toward safety.
    return RISK_TIER.get(tool_name, "red")

The default of "red" is the whole point. Add a new tool and forget to register its tier, and it will never quietly auto-run. A safe default is a design that assumes gaps will happen.

Next, the function declarations Gemini sees, and the executors that do the work. This uses the google-genai SDK.

from google import genai
from google.genai import types
 
# Declarations (the schema the model sees)
tools = types.Tool(function_declarations=[
    types.FunctionDeclaration(
        name="search_docs",
        description="Keyword-search internal documents",
        parameters=types.Schema(
            type=types.Type.OBJECT,
            properties={"query": types.Schema(type=types.Type.STRING)},
            required=["query"],
        ),
    ),
    types.FunctionDeclaration(
        name="delete_old_backups",
        description="Delete backups older than the given number of days",
        parameters=types.Schema(
            type=types.Type.OBJECT,
            properties={"days": types.Schema(type=types.Type.INTEGER)},
            required=["days"],
        ),
    ),
])
 
# The real work (only called once a call clears the gate)
def _search_docs(query: str) -> dict:
    return {"hits": [{"title": "Ops runbook", "score": 0.82}]}
 
def _delete_old_backups(days: int) -> dict:
    return {"deleted": 3, "days": days}
 
EXECUTORS = {
    "search_docs": _search_docs,
    "delete_old_backups": _delete_old_backups,
}

Declarations and executors live in separate dictionaries because the gate needs a state where a tool is "declared but not yet executed." When the model asks to call a red tool, we hold it without ever touching the executor.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Tag tools green/yellow/red and gate only the dangerous calls behind a human approval step
Persist a pending approval as JSON, approve it the next morning, and resume the loop from where it paused
Real pitfalls: rejecting one of several parallel calls, tool_config ANY that never terminates, and stale saved arguments
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Advanced2026-06-29
When Your Gemini Agent Has Three Tool Routes and Quietly Picks the Wrong One
Put Function Calling, Code Execution, and Grounding into one agent and the model will sometimes choose the wrong route, while the output still looks perfectly plausible. Here is how I instrument route selection and correct it with phase separation and verification gates, with working code.
Advanced2026-04-25
Building Self-Critiquing Agents with Gemini API: A Production-Ready Guide to Reflection and Critic-Refiner Patterns
A production-grade walkthrough of Reflection and Critic-Refiner patterns with Gemini 3 Pro and 2.5 Flash. Covers implementation, cost guards, over-correction defenses, and monitoring signals from real deployments.
Advanced2026-03-17
Production-Grade Voice AI Agent with Gemini Live API & Google ADK [2026]
Build and deploy a production-grade voice AI agent by combining Gemini Live API with Google ADK, Function Calling, WebSocket management, and Cloud Run. Covers architecture design, connection stability, parallel tool execution, and cost optimization.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →