HomeAbout MeBook a Call

Architecting Human in the Loop AI Approvals with Google Sheets

By Vo Tu Duc
May 05, 2026
Architecting Human in the Loop AI Approvals with Google Sheets

The very autonomy that makes AI so powerful also makes it inherently risky. Discover why strategic human oversight is not a bottleneck, but the key to building a truly robust and accountable system.

image 0

The Imperative for Oversight in AI [Automated Job Creation in Real Time Jobber and Google Sheets Integration from Gmail](https://votuduc.com/Automated-Job-Creation-in-Jobber-from-Gmail-p115606)

The allure of fully autonomous AI systems is undeniable. They promise a future of hyper-efficiency, where mundane tasks vanish and operational costs plummet. Yet, as we integrate these powerful models deeper into our core business processes, we confront a critical paradox: the very autonomy that makes AI so powerful also makes it inherently risky. Placing blind trust in an algorithm, especially when the stakes are high, is not a strategy—it’s a gamble. True enterprise-grade AI implementation requires a more nuanced approach, one that strategically embraces human oversight not as a bottleneck, but as an essential component of a robust, reliable, and accountable system.

The Double-Edged Sword of Autonomous AI in Business

Artificial intelligence is a profoundly transformative technology. It can analyze millions of data points to detect fraud, categorize customer support tickets with superhuman speed, generate draft legal clauses, and automate complex financial reconciliations. This is one edge of the sword—a powerful tool for scaling operations and unlocking unprecedented productivity.

The other edge, however, is sharp and dangerous.

image 1

Why Human Judgment is Critical for High-Stakes Workflows

The need for oversight becomes non-negotiable in what we can term “high-stakes workflows.” These are processes where an error has significant financial, legal, reputational, or ethical consequences. Examples are everywhere in business:

  • Financial Approvals: Authorizing payments over a certain threshold, flagging large transactions for regulatory review, or approving customer refunds.

  • Legal & Compliance: Redacting sensitive information from documents, classifying contracts based on risk, or ensuring marketing copy adheres to regulatory standards.

  • Critical Customer Communications: Responding to a high-value client’s formal complaint, issuing a public statement, or resolving a sensitive HR issue.

In these domains, AI can and should serve as a powerful assistant, but the final decision requires human faculties that models currently lack. A human expert provides contextual understanding, interpreting not just the data on the screen but also the unwritten business rules, the history of a client relationship, or the strategic importance of a particular transaction. They apply ethical reasoning, making judgments that are not just computationally optimal but also fair and right. Most importantly, humans provide accountability. When a decision is made, a person can stand behind it, explain the reasoning, and take responsibility—a fundamental requirement for any trustworthy business operation.

Introducing the Human-in-the-Loop (HITL) Pattern

To resolve the tension between Automated Quote Generation and Delivery System for Jobber and risk, we turn to a proven architectural pattern: Human-in-the-Loop (HITL). This is not simply a manual review queue; it is a deliberate system design that intelligently fuses machine-scale processing with human cognitive strengths.

The core principle of HITL is to automate the predictable and escalate the ambiguous. A typical HITL workflow operates as follows:

  1. Process & Predict: An AI model performs a task, such as classifying a document, extracting data, or recommending an action. Crucially, it also outputs a confidence score for its prediction.

  2. Route for Review: A rules engine evaluates the output. If the confidence score is below a predefined threshold, or if the task involves a high-stakes category (e.g., an invoice over $10,000), the task is automatically routed to a human expert for review. Low-confidence and high-risk items are filtered out for oversight, while high-confidence, low-risk items can be processed automatically.

  3. Adjudicate & Act: The human reviewer examines the AI’s output within a dedicated interface. They can approve it, reject it, or correct it. Their final decision is the one that the system executes.

  4. Feedback & Improve: This is the most powerful aspect of the HITL pattern. The human’s corrections are not just ephemeral fixes; they are captured as high-quality, labeled data. This data can be fed back into the system to fine-tune and retrain the AI model, creating a virtuous cycle of continuous improvement. Over time, the model becomes more accurate, requiring less human intervention.

By implementing an HITL pattern, we transform AI from a fragile, autonomous black box into a robust, collaborative tool. It allows us to harness the speed of Automated Work Order Processing for UPS without abdicating the critical responsibilities of judgment and accountability.

Core Architecture: The AI-to-Sheet-to-Human Cycle

Before we dive into the code, let’s establish a solid mental model of the architecture. At its heart, this system is a simple, elegant, and surprisingly robust feedback loop. Think of it as a digital assembly line where an AI creates a component, places it on a conveyor belt (the Google Sheet), and then pauses for a human quality assurance check before the component is shipped to its final destination. This cycle ensures that automation accelerates your workflow, but human judgment remains the ultimate gatekeeper.

A High-Level Blueprint of the Approval Workflow

The entire process is event-driven, with each step triggering the next. A single piece of AI-generated content flows through the system as follows:

  1. Generation & Ingestion: An external process—be it a JSON-to-Video Automated Rendering Engine script calling a Large Language Model (LLM), a Zapier automation, or a serverless function—generates a piece of content. This content, along with any relevant metadata (e.g., timestamp, source prompt, confidence score), is programmatically inserted as a new row into our Google Sheet.

  2. State Initialization & Notification: The new row is created with a default status, such as PENDING_REVIEW. An Apps Script trigger detects this new entry and immediately fires. Its job is to parse the row’s data and dispatch a notification—typically an email—to the designated human reviewer.

  3. Human Review & Decision: The reviewer receives the email, which contains a summary of the content and, crucially, a direct link to the specific row in the Google Sheet. They click the link, examine the AI’s output in the context of the full dataset, and make a decision.

  4. State Transition: The reviewer’s decision is recorded by changing the value in the “Status” column for that row. They might select APPROVED or REJECTED from a data validation dropdown.

  5. Downstream Action: This manual edit is the most critical event in the loop. The onEdit trigger in AI Powered Cover Letter Automation Engine instantly detects this state change. It then executes the corresponding business logic:

  • If APPROVED, the script might call an external API to publish the content to a CMS, add the data to a production database, or send a customer-facing email.

  • If REJECTED, the script could move the row to an “Archive” sheet, log the reason for rejection, or even trigger a request to the AI to regenerate the content with feedback.

This closed-loop system provides a complete audit trail, decouples the AI generation from the final action, and places the human squarely in control.

Component 1: [Automated Web Scraping with [Multilingual Text-to-Speech Tool with SocialSheet Streamline Your Social Media Posting 123](https://votuduc.com/Multilingual-Text-to-Speech-Tool-with-Google-Workspace-p809282)](https://votuduc.com/Automated-Web-Scraping-with-Google-Sheets-p292968) as the Central State Machine

It’s tempting to dismiss Google Sheets as “just a spreadsheet,” but in this architecture, we elevate it to a more powerful role: a lightweight, visual, and collaborative state machine.

Each row in the sheet represents a single object or task (e.g., a generated blog post outline, a summarized customer ticket, a proposed social media update). The “Status” column is the most important field, as it defines the current state of that object within our workflow.

Using Google Sheets as our central hub provides several key advantages:

  • Zero-Cost Database & UI: It serves as both a persistent data store and an instantly accessible user interface. There’s no need to build a custom front-end or provision a database. Your reviewers already know how to use it.

  • Transparent State Management: Anyone with access can see the status of every single item in the queue at a glance. It’s a living dashboard of the entire process.

  • Rich Data Context: Unlike a simple task queue, a sheet allows you to store rich context alongside the item to be reviewed. You can have columns for the original prompt, AI confidence scores, reviewer comments, timestamps, and more, all in one consolidated view.

  • Audit Trail by Default: The version history of a Google Sheet provides an immutable log of every change, showing who approved or rejected an item and when.

By treating each row as an object and the “Status” column as its state, the spreadsheet becomes the canonical source of truth for our entire human-in-the-loop process.

Component 2: Genesis Engine AI Powered Content to Video Production Pipeline as the Intelligent Orchestrator

If Google Sheets is the state machine, [Architecting Multi Tenant AI Workflows in Building Modular Agentic Apps Script with Gemini Function Calling](https://votuduc.com/architecting-multi-tenant-ai-workflows-in-google-apps-script-p-20260321290501) is the engine that drives the state transitions. This serverless JavaScript platform is the “glue” that connects our components and automates the workflow. It lives inside your Google Sheet (accessible via Extensions > Apps Script) and acts as the system’s intelligent orchestrator.

Its power comes from two primary capabilities:

  1. Event-Driven Triggers: Apps Script can execute code in response to specific events. For this architecture, we rely heavily on:
  • onEdit(e): A simple trigger that runs automatically whenever a user changes the value of any cell. We use this to detect when a reviewer changes a status from PENDING_REVIEW to APPROVED, immediately kicking off the downstream action.

  • Programmatic Triggers: We can create triggers that run when the sheet is first opened (onOpen), when a form is submitted, or on a time-based schedule (e.g., “send a summary digest every morning at 9 AM”).

  • Web App Endpoint (doPost(e)): This is the crucial ingress point. By deploying a script as a web app, Apps Script generates a unique URL. Our external AI system can send a POST request with the generated content to this URL, allowing Apps Script to catch the data and write it to the sheet.

  1. Seamless Integration: Apps Script is a first-class citizen in the Automatically create new folders in Google Drive, generate templates in new folders, fill out text automatically in new files, and save info in Google Sheets ecosystem and a capable tool for connecting to the wider web.
  • Native Services: It has built-in services for controlling Gmail (MailApp), Google Drive (DriveApp), and other Sheets (SpreadsheetApp).

  • External APIs: Using the UrlFetchApp service, it can make HTTP requests to virtually any external API, allowing you to connect your approval workflow to services like Automated Lead Capture and Notification System for Planner Bee, Slack, Twitter, or your own internal systems.

In short, Apps Script is the serverless backend that listens for events in the sheet and executes the business logic that makes the whole system work.

Component 3: Email as the Human Interface

While the Google Sheet is where the decision is made, it’s a “pull” system—the reviewer has to remember to go and check it. To create a timely and efficient process, we need a “push” mechanism. This is where email comes in.

Email serves as the primary notification and action-initiation channel for the human in the loop. A well-crafted notification email generated by Apps Script is far more than a simple alert; it’s an actionable part of the interface.

Here’s what makes it effective:

  • Push-Based Notification: It actively informs the reviewer that their input is required, reducing latency in the approval process. The task doesn’t sit in the queue unnoticed.

  • Contextual Information: The email body can be dynamically populated with key information from the sheet row, such as a snippet of the AI-generated text or the source prompt. This gives the reviewer enough context to decide if they need to review it immediately.

  • The “Magic Link”: The most important element is a direct hyperlink to the exact row needing review. You can construct a URL like https://docs.google.com/spreadsheets/d/SHEET_ID/edit#gid=0&range=A52 that, when clicked, opens the sheet and automatically highlights the correct cell. This simple feature drastically improves the user experience and reduces friction.

  • Ubiquity and Accessibility: Every stakeholder has an email client, and it works on every device. There is no need to install special software or learn a new platform.

By using email as the trigger for human action, we bridge the gap between the automated system and the human reviewer, ensuring that tasks are addressed promptly and efficiently.

Step-by-Step Implementation Guide

Alright, let’s roll up our sleeves and build this thing. The beauty of this architecture lies in its simplicity and the use of readily available, powerful tools. We’ll treat Google Sheets not just as a spreadsheet, but as our central state machine and database. Google Apps Script will act as the serverless orchestration layer, connecting our AI’s brain to the human’s judgment.

Step 1: The AI Agent Pauses and Logs Draft Output to a Sheet

The first critical moment in our workflow is the “pause.” Your AI agent, after completing a task that requires validation—like drafting a sensitive client email, generating a complex configuration file, or summarizing legal documents—must stop and hand over the baton. Instead of proceeding, it serializes its state and proposed output into a new row in our designated Google Sheet.

This Sheet is our single source of truth. A well-defined schema is crucial for a robust system. Consider the following columns:

| Column Name | Purpose | Example |

| :--- | :--- | :--- |

| request_id | A unique identifier for the entire task. | task-20231027-a8b4 |

| timestamp_created | When the approval request was logged. | 2023-10-27T10:00:00Z |

| status | The current state of the request. This is key. | PENDING_APPROVAL |

| approver_email | The email address of the designated human approver. | [email protected] |

| ai_draft_output | The content generated by the AI that needs review. | Subject: Q4 Project Proposal... |

| approval_token | A unique, unguessable token for this specific request. | uuid-v4-string |

| final_output | To be populated after approval. | (empty) |

| timestamp_updated | When the status was last changed. | 2023-10-27T10:00:00Z |

The approval_token is particularly important. It acts as a secure key for the approval action, ensuring that only someone with the link can act on this specific request.

Here’s a Python snippet showing how an AI agent might use the gspread library to log its output. The agent generates a UUID for the token and writes the new row, setting the initial status to PENDING_APPROVAL.


import gspread

import uuid

from datetime import datetime, timezone

# --- Authenticate with Google Sheets (setup not shown) ---

# gc = gspread.service_account()

# sheet = gc.open("AI_Approvals").worksheet("Sheet1")

def request_human_approval(request_id, draft_content, approver):

"""

Pauses the AI workflow and logs the draft to Google Sheets for approval.

"""

print(f"Pausing workflow for request_id: {request_id}. Awaiting human approval.")

approval_token = str(uuid.uuid4())

timestamp = datetime.now(timezone.utc).isoformat()

new_row = [

request_id,

timestamp,

"PENDING_APPROVAL",

approver,

draft_content,

approval_token,

"", # final_output is initially empty

timestamp

]

sheet.append_row(new_row)

# The agent's process for this task now stops.

# It will rely on a separate polling mechanism to know when to resume.

return {"status": "success", "message": "Approval request logged."}

# --- Example Usage ---

# ai_generated_email = "Subject: Q4 Project Proposal\n\nHi Team,\nPlease review the attached..."

# request_human_approval("task-20231027-a8b4", ai_generated_email, "[email protected]")

At this point, the agent’s job for this task is done. It has successfully handed off the state. It doesn’t wait in an active loop; instead, a separate process will later poll the sheet to see if a decision has been made.

Step 2: An Apps Script Trigger Detects the New Entry and Dispatches an Approval Email

This is where the magic of automation kicks in. We’ll use Google Apps Script, the JavaScript-based cloud scripting platform inside AC2F Streamline Your Google Drive Workflow, to monitor our sheet.

While you could use an onEdit trigger, they can be brittle and fire too frequently. A more robust pattern is a time-driven trigger that runs a function periodically (e.g., every 5 minutes). This function scans for any rows in the PENDING_APPROVAL state that haven’t been processed yet.

Here’s the plan for our script:

  1. Set up a Trigger: In the Apps Script editor, create a time-driven trigger that executes a function, say processPendingApprovals, every 5 minutes.

  2. The processPendingApprovals function:

  • Gets all data from the sheet.

  • Loops through each row.

  • If a row’s status is PENDING_APPROVAL, it constructs and sends a formatted email.

  • Crucially, it then updates the row’s status to APPROVAL_SENT to prevent sending duplicate emails on the next run.

The email itself will contain two specially crafted links: “Approve” and “Reject”. These links point to our deployed Apps Script web app, passing the approval_token and the desired action as URL parameters.


// Google Apps Script Code (Code.gs)

const SHEET_ID = "YOUR_SHEET_ID"; // Or use SpreadsheetApp.getActiveSpreadsheet()

const SHEET_NAME = "Sheet1";

// This is the URL of your deployed Apps Script Web App

const WEB_APP_URL = "https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec";

function processPendingApprovals() {

const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);

const data = sheet.getDataRange().getValues();

const headers = data.shift(); // Get header row

// Find column indices to make code more readable

const statusCol = headers.indexOf('status');

const emailCol = headers.indexOf('approver_email');

const draftCol = headers.indexOf('ai_draft_output');

const tokenCol = headers.indexOf('approval_token');

data.forEach((row, index) => {

if (row[statusCol] === 'PENDING_APPROVAL') {

const approverEmail = row[emailCol];

const draftContent = row[draftCol];

const token = row[tokenCol];

// Construct the approval and rejection links

const approveUrl = `${WEB_APP_URL}?action=approve&token=${token}`;

const rejectUrl = `${WEB_APP_URL}?action=reject&token=${token}`;

const subject = `AI Action Required: Approval for Request`;

const body = `

<p>An AI agent has generated the following content and requires your approval to proceed.</p>

<hr>

<pre style="white-space: pre-wrap; background-color: #f4f4f4; padding: 15px; border-radius: 5px;">${draftContent}</pre>

<hr>

<p>Please review and choose an action:</p>

<a href="${approveUrl}" style="background-color: #4CAF50; color: white; padding: 14px 25px; text-align: center; text-decoration: none; display: inline-block; border-radius: 5px; margin-right: 10px;">Approve</a>

<a href="${rejectUrl}" style="background-color: #f44336; color: white; padding: 14px 25px; text-align: center; text-decoration: none; display: inline-block; border-radius: 5px;">Reject</a>

`;

MailApp.sendEmail({

to: approverEmail,

subject: subject,

htmlBody: body

});

// IMPORTANT: Update status to prevent re-sending emails

// Note: index is 0-based for array, but sheet rows are 1-based. Headers were shifted.

sheet.getRange(index + 2, statusCol + 1).setValue('APPROVAL_SENT');

}

});

}

Step 3: The Manager Approves or Rejects Directly from their Inbox

The experience for the manager is seamless. They receive the email, review the AI’s work directly in the message, and click either “Approve” or “Reject”. This click is the human intervention that closes the loop.

When a link is clicked, it sends a GET request to our Apps Script web app’s URL. We need to implement the doGet(e) function, which is the standard entry point for handling such requests in Apps Script.

This function will:

  1. Parse the action and token from the event parameter e.

  2. Perform a security check: Find the row in the sheet corresponding to the token. It’s vital to ensure the token is valid and the request is still in the APPROVAL_SENT state. This prevents acting on already completed or invalid requests.

  3. Call a helper function to update the sheet based on the action.

  4. Return a simple, user-friendly HTML page to the manager confirming their action was received.

To make this work, you must deploy your script as a Web App. Go to Deploy > New deployment, select Web app, and ensure it’s configured to be executed by “Me” but accessible to “Anyone, even anonymous”. This is necessary because Google executes the link click from their own servers, not from the manager’s logged-in account. The security is handled by our unguessable token, not user authentication.


// Add this function to your Code.gs file

function doGet(e) {

const action = e.parameter.action;

const token = e.parameter.token;

if (!action || !token) {

return ContentService.createTextOutput("Error: Missing action or token.").setMimeType(ContentService.MimeType.TEXT);

}

try {

const message = updateSheetBasedOnAction(token, action);

return HtmlService.createHtmlOutput(`

<body style="font-family: sans-serif; text-align: center; padding-top: 50px;">

<h1>Thank You!</h1>

<p>${message}</p>

</body>

`);

} catch (error) {

return HtmlService.createHtmlOutput(`

<body style="font-family: sans-serif; text-align: center; padding-top: 50px; color: red;">

<h1>An Error Occurred</h1>

<p>${error.message}</p>

</body>

`);

}

}

Step 4: The Script Updates the Sheet and Signals the AI Agent to Resume or Revise

The doGet(e) function orchestrates this final step. It finds the correct row and updates the state, officially recording the human’s decision. This is the “signal” that the AI agent has been waiting for.

The updateSheetBasedOnAction helper function is where the state change happens in our sheet.

  • On Approval: The status is changed to APPROVED, and the ai_draft_output is copied to the final_output column.

  • On Rejection: The status is simply changed to REJECTED.


// Add this helper function to your Code.gs file

function updateSheetBasedOnAction(token, action) {

const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);

const data = sheet.getDataRange().getValues();

const headers = data.shift();

const tokenCol = headers.indexOf('approval_token');

const statusCol = headers.indexOf('status');

const draftCol = headers.indexOf('ai_draft_output');

const finalCol = headers.indexOf('final_output');

const updatedCol = headers.indexOf('timestamp_updated');

let rowIndex = -1;

for (let i = 0; i < data.length; i++) {

if (data[i][tokenCol] === token) {

rowIndex = i;

break;

}

}

if (rowIndex === -1) {

throw new Error("Invalid or expired approval token.");

}

const currentStatus = data[rowIndex][statusCol];

if (currentStatus !== 'APPROVAL_SENT') {

throw new Error(`This request has already been processed. Current status: ${currentStatus}`);

}

const sheetRow = rowIndex + 2; // +1 for 1-based index, +1 for header

const timestamp = new Date().toISOString();

let message = "";

if (action === 'approve') {

sheet.getRange(sheetRow, statusCol + 1).setValue('APPROVED');

const draftContent = sheet.getRange(sheetRow, draftCol + 1).getValue();

sheet.getRange(sheetRow, finalCol + 1).setValue(draftContent);

message = "The request has been successfully APPROVED.";

} else if (action === 'reject') {

sheet.getRange(sheetRow, statusCol + 1).setValue('REJECTED');

message = "The request has been REJECTED.";

} else {

throw new Error("Invalid action specified.");

}

sheet.getRange(sheetRow, updatedCol + 1).setValue(timestamp);

return message;

}

How does the AI agent know to resume?

With the sheet updated, the final piece is for the AI agent to become aware of the decision. We’ll use a simple and highly reliable polling mechanism. A separate, long-running process on the AI agent’s side periodically queries the sheet for rows that have been updated to APPROVED or REJECTED.


# Python code for the AI agent's polling mechanism

import time

# --- Authenticate with Google Sheets (setup not shown) ---

# sheet = gc.open("AI_Approvals").worksheet("Sheet1")

def poll_for_updates():

"""

Periodically checks the Google Sheet for decisions on pending tasks.

"""

print("Starting polling for approval updates...")

while True:

all_records = sheet.get_all_records() # Gets data as a list of dicts

for record in all_records:

if record['status'] in ['APPROVED', 'REJECTED']:

# Find the corresponding task in your system using request_id

request_id = record['request_id']

status = record['status']

print(f"Decision received for {request_id}: {status}")

if status == 'APPROVED':

# Resume the workflow with the final_output

final_content = record['final_output']

# execute_next_step(request_id, final_content)

pass

elif status == 'REJECTED':

# Trigger a revision process or notify the user

# handle_rejection(request_id)

pass

# IMPORTANT: Mark the row as processed to avoid acting on it again

# For example, update status to 'PROCESSED' or 'CLOSED'

# (This requires finding the row number and updating it)

time.sleep(60) # Wait for 60 seconds before polling again

# This function would typically run in a background thread or a separate microservice.

# poll_for_updates()

And there you have it. A full round trip. The AI pauses, the script creates a decision point, a human makes a choice from their email, the sheet’s state is updated, and the AI polls to pick up its work and carry on.

Advanced Considerations for Robust Systems

Moving from a proof-of-concept to a production-grade Human-in-the-Loop (HITL) system requires thinking beyond the “happy path.” What happens when a script times out? How do you secure the data? What if you need to scale to multiple teams? Let’s fortify our Google Sheets-based architecture by addressing these critical questions.

Building a Resilient Resume Pattern in Apps Script

Google Apps Script imposes execution time limits—typically 6 minutes for a standard Gmail account and 30 minutes for Automated Client Onboarding with Google Forms and Google Drive. accounts. If your AI processing or data fetching takes longer, your script will unceremoniously terminate, leaving your system in an inconsistent state. A single long-running task can bring the whole workflow to a halt.

The solution is to design for interruption. Instead of a single, monolithic script that tries to do everything at once, we implement a resume pattern. The core idea is to break the work into small, manageable chunks and save our progress after each one.

Here’s how it works:

  1. State Management: We use PropertiesService—a simple key-value store built into Apps Script—to keep track of our progress. This could be the index of the last row we successfully processed.

  2. Batch Processing: The script is designed to run, process a small batch of rows (e.g., 10-20), and then update its state in PropertiesService.

  3. Self-Termination and Scheduling: Crucially, before it hits the execution limit, the script checks the remaining time. If it’s running low, it saves its state and programmatically creates a new time-based trigger to run itself again in a few minutes. It then gracefully exits.

  4. Graceful Resumption: When the new trigger fires the script, the first thing it does is read its last known state from PropertiesService and picks up exactly where it left off.

Here is a conceptual code snippet illustrating the pattern:


// A conceptual example of the resume pattern in Apps Script

function processPendingApprovals() {

const scriptProperties = PropertiesService.getScriptProperties();

const lock = LockService.getScriptLock();

// Use a lock to prevent concurrent executions from clashing

if (!lock.tryLock(10000)) {

console.log('Could not obtain lock. Another process is likely running.');

return;

}

try {

// Read the last processed row, defaulting to 1 (header row)

let lastRow = parseInt(scriptProperties.getProperty('lastProcessedRow')) || 1;

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Approvals');

const data = sheet.getRange(lastRow + 1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();

for (let i = 0; i < data.length; i++) {

const currentRow = lastRow + 1 + i;

const rowData = data[i];

const status = rowData[/ *status_column_index* /];

if (status === 'PENDING_AI') {

// --- Your AI processing logic here ---

// processRow(rowData);

console.log(`Processed row ${currentRow}`);

}

// Save progress after each row

scriptProperties.setProperty('lastProcessedRow', currentRow);

// Check if we're running out of time (e.g., less than 60 seconds left)

if (ScriptApp.getRemainingTimeInMillis() < 60000) {

console.log('Execution time is low. Scheduling continuation and exiting.');

// Create a trigger to run this function again in 5 minutes

ScriptApp.newTrigger('processPendingApprovals')

.timeBased()

.after(5  *60*  1000)

.create();

return; // Exit gracefully

}

}

// If we finish all rows, reset for the next run

scriptProperties.deleteProperty('lastProcessedRow');

console.log('All pending approvals processed.');

} catch (e) {

console.error(`An error occurred: ${e.toString()}`);

// Handle errors (see Error Handling section)

} finally {

lock.releaseLock();

}

}

This pattern transforms your script from a fragile, short-lived process into a durable, unstoppable worker that can chew through thousands of rows without breaking a sweat.

Managing Permissions and Security for the Workflow

When you’re dealing with potentially sensitive data and automated actions, security is not an afterthought—it’s a foundational requirement. A naive implementation using a simple onEdit trigger is a major security risk because the script runs with the permissions of the user who made the edit.

Here’s how to lock down your workflow:

  1. Decouple Triggering from Execution: Do not put your core logic inside an onEdit trigger. Instead, use the onEdit function for one simple task: changing a cell’s status (e.g., from APPROVED to PENDING_EXECUTION).

  2. Execute as a Trusted Identity: Create a time-based, installable trigger that runs your main processing function (like the one above) every few minutes. When you create an installable trigger, it runs under your identity (the developer), not the user editing the sheet. This ensures the script always has the correct, predictable permissions to call APIs and modify protected data.

  3. Use Protected Ranges and Sheets: Your Google Sheet is a user interface. Control it.

  • Lock Down AI/System Columns: Columns that display the AI’s output, processing status, timestamps, or record IDs should be protected. Only you (the sheet owner) and, by extension, your script should be able to edit them.

  • Allow User Input Only Where Needed: Leave the columns for the human decision (e.g., an “Approval” column with a dropdown for “Approve”/“Reject”) editable by the relevant approvers.

  1. Principle of Least Privilege for Scopes: In your Apps Script manifest file (appsscript.json), be explicit and minimal with your OAuth scopes. If your script only needs to access the spreadsheet it’s bound to, use https://www.googleapis.com/auth/spreadsheets.currentonly instead of the broader https://www.googleapis.com/auth/spreadsheets. This prevents your script from having unintended access to other user files.

Think of it like a restaurant: the approver is the customer placing an order. The onEdit trigger is the waiter putting the order slip on the counter. The installable trigger is the authorized chef who is the only one allowed in the kitchen to actually cook the meal.

Scaling the Architecture for Multiple Agents and Approval Tiers

A single approval queue quickly becomes a bottleneck. Real-world workflows involve different teams and multi-step processes (e.g., a manager’s approval followed by a director’s). Your Google Sheets architecture can scale to handle this complexity.

For Multiple Teams/Agents:

  • Routing Column: Add a column like AssignedTeam or CurrentApprover. Your central processing script can use this column to route tasks.

  • Dedicated Views: For each team, create a separate tab in your spreadsheet that uses a FILTER or QUERY formula to show only the rows assigned to them. This gives each team a clean, dedicated workspace without needing complex scripting.

  • Notification Logic: When a task is assigned, your script should use the AssignedTeam or CurrentApprover column to determine who to notify via email or Google Chat.

For Multi-Tier Approvals:

This requires evolving your data model into a simple state machine.

  1. Add a Status Column: This is the most important field. It will track the request’s journey through the workflow. Statuses could include: PENDING_MANAGER, PENDING_DIRECTOR, APPROVED, REJECTED.

  2. Add a History Column: To maintain an audit trail, log every action in a History column. You can store a JSON array of objects, with each object containing the approver, their decision, and a timestamp.

  3. State Transition Logic: Your script’s main function becomes a state machine.

  • If a row has a status of PENDING_MANAGER and the manager’s decision is “Approve,” the script updates the status to PENDING_DIRECTOR.

  • It then looks up who the director is (perhaps from another sheet or a hardcoded logic) and sends them a notification.

  • It logs this action to the History column.

This approach turns your simple spreadsheet into a surprisingly powerful and auditable workflow engine.

Error Handling and Escalation Paths

A system that fails silently is a system you can’t trust. Robust error handling ensures that when things go wrong—an API is down, data is malformed, a permission is denied—the issue is logged, isolated, and escalated to a human for resolution.

  1. Aggressive try...catch Blocks: Wrap every fallible operation, especially external API calls (UrlFetchApp) or complex data manipulations, in a try...catch block.

  2. Create a Dead-Letter Queue (DLQ): Don’t let a single bad row stop the entire process. When your catch block catches an unrecoverable error for a specific row, implement a DLQ pattern:

  • Create a new tab in your spreadsheet named “Error Log” or “DLQ”.

  • In the catch block, copy the entire problematic row to this “Error Log” sheet.

  • Add extra columns for the error message and a timestamp.

  • Delete the problematic row from the main processing sheet (or update its status to ERROR) so the script doesn’t try to process it again and again.

  1. Proactive Alerting: Logging to a sheet is good, but someone needs to look at it. In your catch block, use MailApp or UrlFetchApp (for Google Chat webhooks) to send an immediate notification to a system administrator or support alias. The alert should include the error message and a link to the “Error Log” sheet.

  2. Implement Retries with Exponential Backoff: For transient network errors or API rate limits, a simple retry can resolve the issue. A more advanced technique is “exponential backoff”—if a call fails, wait 2 seconds and retry; if it fails again, wait 4 seconds, then 8, and so on. This can be complex to implement in Apps Script but can be simulated by using PropertiesService to store a retry count and scheduling a future trigger. For most use cases, the DLQ pattern provides a simpler and more effective solution.

By building these advanced considerations into your system from the start, you elevate your Google Sheets-based workflow from a clever hack into a reliable, secure, and scalable automation asset.

Conclusion: Building Trustworthy AI Systems

We’ve journeyed through the architecture of a system that does more than just automate tasks; it builds a bridge between machine efficiency and human accountability. The true power of AI in the enterprise isn’t about replacing human oversight but augmenting it. By architecting a human-in-the-loop (HITL) process with accessible tools like Google Sheets, you create a symbiotic relationship where AI handles the scale and speed, while humans provide the critical nuance, ethical judgment, and contextual understanding that machines currently lack. This approach transforms a potentially fallible “black box” into a transparent, auditable, and trustworthy operational workflow.

Recap: The Power of Blending AI Efficiency with Human Wisdom

The core takeaway is the immense value found at the intersection of automation and manual review. We’ve demonstrated a practical blueprint that leverages the serverless power of Google Apps Script and the universal accessibility of Google Sheets to create a robust approval system.

This hybrid model delivers a trifecta of benefits:

  • Enhanced Accuracy: It provides a crucial safety net, catching AI errors and hallucinations before they impact your customers, data, or bottom line.

  • Increased Trust & Adoption: Stakeholders are far more likely to embrace AI-driven processes when they know a human expert has the final say on critical decisions. This transparency fosters confidence across the organization.

  • Continuous Improvement Engine: Every approval or rejection is more than just a single decision; it’s a valuable data point. This feedback loop is the fuel for refining your AI’s prompts, fine-tuning models, and systematically reducing the error rate over time.

You’re not just building an approval queue; you’re building a smarter, self-improving system.

Your Next Steps Toward Implementing Safer Automation

Moving from theory to implementation is the most critical step. The architecture we’ve discussed is not just a concept—it’s an actionable plan. Here is a clear roadmap to get started:

  1. Identify Your Critical Juncture: Analyze your existing workflows. Pinpoint the single most impactful process where an AI’s decision carries significant risk or value. Is it approving customer-facing content, validating financial data entries, or qualifying sales leads? Start there.

  2. Build a Minimum Viable PoC: Resist the urge to over-engineer. Use the Google Sheets and Apps Script foundation from this guide to build a simple proof of concept. The goal is to validate the workflow and demonstrate value quickly, not to build a perfect, scalable system from day one.

  3. Define Your Approval Interface: Carefully design your Google Sheet. What specific data does your human reviewer need to see to make a confident “Approve” or “Reject” decision in under 30 seconds? The clarity of this interface is paramount to the system’s efficiency.

  4. Establish the Feedback Loop: From the very first approval, start thinking about how you will use this human-generated data. Is a rejection due to a factual error, a tonal issue, or a misunderstanding of context? Categorizing this feedback is the first step toward making your AI more intelligent.

Ready to scale your architecture? Book a solution discovery call with Vo Tu Duc to audit your specific business needs.

The Google Sheets architecture is an incredibly powerful and cost-effective solution for validating your HITL workflow and handling low-to-medium volume tasks. But what happens when you need to process thousands of decisions per day, require enterprise-grade security, integrate with multiple complex systems, and provide a bespoke user interface for your review team?

When you’re ready to move beyond the prototype stage and build a mission-critical, high-throughput system, the architectural considerations change. Let’s discuss how to transition from a Google Sheet to a robust solution using dedicated databases, custom web applications, and scalable cloud infrastructure.

Book a complimentary solution discovery call with Vo Tu Duc to get a personalized audit of your business requirements and map out a production-ready architecture that is secure, scalable, and built for growth.


Tags

AIHuman in the LoopGoogle SheetsAutomationWorkflow ApprovalAI Governance

Share


Previous Article
Architecting Prompt Injection Defenses for Google Workspace AI Agents
Vo Tu Duc

Vo Tu Duc

A Google Developer Expert, Google Cloud Innovator

Stop Doing Manual Work. Scale with AI.

Hi, I'm Vo Tu Duc (Danny), a recognised Google Developer Expert (GDE). I architect custom AI agents and Google Workspace solutions that help businesses eliminate chaos and save thousands of hours.

Want to turn these blog concepts into production-ready reality for your team?
Book a Discovery Call

Table Of Contents

1
The Imperative for Oversight in AI [Automated Job Creation in Real Time Jobber and Google Sheets Integration from Gmail](https://votuduc.com/Automated-Job-Creation-in-Jobber-from-Gmail-p115606)
2
Core Architecture: The AI-to-Sheet-to-Human Cycle
3
Step-by-Step Implementation Guide
4
Advanced Considerations for Robust Systems
5
Conclusion: Building Trustworthy AI Systems

Portfolios

AI Agentic Workflows
Cloud Engineering
AppSheet Solutions
Change Management
Strategy Playbooks
Product Showcase
Uncategorized
Workspace Automation

Related Posts

Automate Site Defect Punch Lists with Gemini and Google Chat
May 22, 2026
© 2026, All Rights Reserved.
Powered By

Quick Links

Book a CallAbout MeVolunteer Legacy

Social Media