# Guardrails: permissions, review, and cost
An agent that can read your Drive, send email on your behalf, and call paid APIs is a junior employee with your credentials and no manager. Guardrails are the manager. This lesson is about building the review gates, permission boundaries, and cost controls that let a Gemini-powered automation run without becoming a liability.
Every unsupervised agent fails in one of three ways:
1. Wrong action, real consequence. It sends a draft to a customer, deletes the wrong rows, or merges a pull request that breaks production.
2. Over-broad access. It was granted "Drive" when it needed one folder, so a prompt injection or a hallucinated tool call reaches everything.
3. Runaway cost. A loop calls Gemini Pro a thousand times, or a grounded search runs on every row of a 50,000-row sheet.
Guardrails mapmapUsing software to automate repetitive marketing tasks and campaigns, enabling personalisation at scale across channels like email, web, and social.Voir la définition complète → cleanly to these:
Start by narrowing what the agent *can* touch, before you worry about what it *should* do.
When you build an automation in Apps Script (the most common way to wire Gemini into Gmail, Sheets, and Docs), the scopes are declared, not assumed. Pin them explicitly in appsscript.json instead of letting the editor auto-grant broad ones:
{
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/script.external_request"
]
}Note spreadsheets.currentonly (this one sheet, not all sheets) and gmail.compose (create drafts, cannot send). That single difference between gmail.compose and gmail.send is the line between an agent that *proposes* email and one that *sends* it. Default to the narrower scope and force a human to cross the line.
On the Vertex AI side, an agent (for example one built with the Agent Development Kit) runs under a service account. Apply the same discipline with IAM:
roles/aiplatform.user, not Owner.roles/bigquery.dataViewer on the *specific dataset*, not the project.The principle is identity per agent. One service account per automation means you can read the audit log and answer "what did *this* agent do" without untangling shared credentials.
In the consumer Gemini app, the equivalent of scope is which Extensions are enabled (Gmail, Drive, Maps, and so on) and what a Gem is allowed to reference. A Gem cannot grant itself access to a connector you have not turned on. For anything that touches sensitive data, build the automation in Apps Script or Vertex AI where scopes are explicit, not in a Gem where access is broad and account-wide.
Some actions should *always* pause for a human, regardless of how confident the model is. Keep this list short and absolute:
Gemini Code Assist and the Gemini CLI already lean this way: the CLI shows you a proposed shell command or file edit and waits for confirmation before running it. That confirm step is not friction to remove. It is the review gate doing its job. When you build your own agents, copy that pattern instead of bypassing it.
A review gate is a deliberate stop where the agent produces a *proposal*, persists it somewhere visible, and waits for explicit human approval before executing. The agent's job ends at "drafted." A person's job begins at "approve."
The cleanest gate in Workspace is the Gmail draft. The agent writes the email but uses the compose scope, so the message lands in Drafts, not the recipient's inbox. The human reviews and clicks Send. No extra UI required, and the gate is a tool every user already understands.
Here is that pattern end to end: classify an inboundinboundA strategy that attracts prospects organically via valuable content (blog, SEO, social) rather than interrupting them.Voir la définition complète → email with Gemini, then create a *draft* reply that a human must approve.
import os
from google import genai
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
REVIEW_PROMPT = """You are a support triage assistant.
Read the customer email and return JSON:
{"category": "...", "needs_human": true|false, "draft_reply": "..."}
Set needs_human=true for refunds, legal, or anything you are unsure about.
Never promise refunds or commitments in draft_reply."""
def triage(email_body: str) -> dict:
resp = client.models.generate_content(
model="gemini-2.5-flash",
contents=[REVIEW_PROMPT, email_body],
config={"response_mime_type": "application/json"},
)
return resp.parsed
def handle(email_body: str):
result = triage(email_body)
# The agent NEVER sends. It only creates a draft for review.
create_gmail_draft(result["draft_reply"], flagged=result["needs_human"])
return resultTwo guardrails are baked in. The model is told to flag refunds and uncertainty itself (needs_human), and the action ceiling is a draft, not a send. Even if the model misjudges, the worst case is an unreviewed *draft* sitting in a folder, not an unwanted email in a customer's inbox.
Not every item needs the same scrutiny. Route by risk:
Let the model return its own needs_human signal *and* enforce hard rules in code. Never trust the model alone to decide whether something is dangerous, because a prompt injection can flip that flag. The code-level rule ("category == refund always escalates") is the real guardrail; the model's self-assessment is a convenience on top.
Cost runs away quietly. The agent works, the output looks fine, and the bill arrives at month end. Build the controls before you need them.
The single biggest lever is model choice. Gemini Flash costs a fraction of Gemini Pro per tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.Voir la définition complète → and is fast enough for classification, extraction, routing, and summarization. Reserve Pro for genuinely hard reasoning. A good default: every step uses Flash, and you *promote* a step to Pro only when you can name why.
def pick_model(task_complexity: str) -> str:
# Default cheap. Escalate only for hard reasoning.
return "gemini-2.5-pro" if task_complexity == "hard" else "gemini-2.5-flash"Agents loop. A reasoning loop with no ceiling can call the model dozens of times on one task. Always set a hard iteration cap and a per-run tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.Voir la définition complète → budget, and fail closed when you hit it:
MAX_STEPS = 6
def run_agent(task, step=0):
if step >= MAX_STEPS:
return escalate(task, reason="step_limit_reached")
# ... one reasoning/tool step ...On Vertex AI and Google Cloud, set a budget and budget alerts so you get notified (or trigger an automated cap) before spend crosses a threshold. A billing alert is your last line of defense when an in-code guardrail is missed. For APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.Voir la définition complète →-key usage from AI Studio, monitor usage in the console and rotate keys that leak. Budgets do not stop spend by themselves, so pair them with the in-code caps above.
Vérification des acquis
1. According to the lesson, what is the key difference between the OAuth scopes `gmail.compose` and `gmail.send`?
2. The lesson maps the three guardrail controls to three failure modes. Which control addresses the 'over-broad access' failure mode?
3. In Apps Script, why does the lesson recommend pinning OAuth scopes explicitly in `appsscript.json` rather than letting the editor auto-grant them?
4. Select ALL correct answers about the three failure modes of unsupervised agents described in the lesson.
Sélectionnez toutes les réponses correctes.
5. Select ALL correct answers about cost-related guardrails and Gemini usage.
Sélectionnez toutes les réponses correctes.
Here is how the three controls combine in one realistic automation. A weekly automation reads a sales Sheet, drafts a summary email to leadership, and waits for approval.
spreadsheets.currentonly and gmail.compose. It can read this one sheet and create a draft. It cannot send, and it cannot touch other files.No part of this can email anyone, spend meaningfully, or change data without a human. That is the goal: an automation where the *worst realistic failure* is a draft someone deletes. Design every agent so its failure mode is harmless by construction, not by hoping the model behaves.
Log every agent action: the input, the model and tokenstokensA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.Voir la définition complète → used, the proposed action, and whether a human approved it. When something goes wrong (and eventually something will), the log is how you find out what happened and prove what did not. In Apps Script, write to a dedicated log sheet or console.log to Cloud Logging. On Vertex AI, use ADK callbacks to capture each tool call. An action your system cannot explain is an action you cannot trust.
gmail.compose not gmail.send, one dataset not the project), and run one identity per agent so the audit log is readable.