H1molino-index.google-tools.execution.skills.md
version: 2026.3 status: locked scope: nextjs / prisma / apps-script / external-tools / orchestration-layer / execution-contract
H11. SYSTEM IDENTITY
Name: Google Tools Execution Layer
Purpose: Provide a deterministic, two-tier execution system bridging Next.js (authority) with Apps Script (external execution)
Layer Position:
UI → Intent → Actions → External Tools (GAS) → Result → DB → Revalidate → Projection
Authority Alignment (inherits from core system):
Reference: 
Next Actions = authority
Prisma = truth
Apps Script = execution only
UI = intent only
⸻
2. ACTIVATION CRITERIA
Use when:
* interacting with Google Workspace (Docs, Gmail, Sheets, Calendar, Drive)
* producing external artifacts (PDFs, emails, events, docs)
* executing real-world side effects
* building toolbar commands or orchestrators
Do NOT use when:
* computing business logic
* mutating Prisma directly (use actions)
* rendering UI
* building internal-only flows
⸻
3. CORE INVARIANTS
Apps Script = stateless executor
Next Actions = orchestration + persistence
UI = trigger only
Hard Rules:
* NEVER call Google APIs directly from UI
* NEVER put business logic in Apps Script
* NEVER persist external results outside Prisma
* ALWAYS wrap GAS calls through server actions
* ALWAYS return structured JSON responses
* ALWAYS attach requestId for traceability
* ALWAYS revalidate after mutation
⸻
4. TWO-TIER ARCHITECTURE
Tier 1 — Atomic Google Tool Layer (GAS)
Purpose:
* generic reusable capability endpoints
* no domain knowledge
Examples:
docs.create
docs.exportPdf
gmail.send
calendar.create
sheets.append
drive.share
drive.list
⸻
Tier 2 — Domain Orchestrators (Next Actions)
Purpose:
* business logic
* entity-aware flows
* multi-step execution
Examples:
createTripAgendaAndInviteGuests
publishOfferAsPdfAndEmail
buildClientBriefAndShare
sendPostSessionSummary
createMeetingPack
⸻
5. REQUEST / RESPONSE CONTRACT
5.1 Request Envelope
type GoogleToolRequest<TInput = unknown> = {
action: string;
requestId: string;
source: "folio" | "trip" | "product" | "experience" | "offer" | "assistant";
entityType?: string;
entityId?: string | number;
documentId?: number;
userId?: string;
payload: TInput;
};
⸻
5.2 Response Envelope
type GoogleToolResponse<TResult = unknown> = {
ok: boolean;
action: string;
requestId: string;
timestamp: string;
result?: TResult;
error?: {
code: string;
message: string;
details?: unknown;
};
meta?: {
documentId?: string;
fileId?: string;
fileUrl?: string;
spreadsheetId?: string;
calendarEventId?: string;
gmailMessageId?: string;
rowsProcessed?: number;
batchComplete?: boolean;
nextCursor?: string | number | null;
durationMs?: number;
};
};
⸻
6. APPS SCRIPT ROUTER (CANONICAL)
function doPost(e) {
const startedAt = Date.now();
try {
const body = JSON.parse(e.postData.contents || "{}");
const action = body.action;
const requestId = body.requestId || Utilities.getUuid();
let result;
switch (action) {
case "docs.create":
result = DocsHandlers.create(body.payload || {});
break;
case "docs.exportPdf":
result = DocsHandlers.exportPdf(body.payload || {});
break;
case "gmail.send":
result = GmailHandlers.send(body.payload || {});
break;
case "calendar.create":
result = CalendarHandlers.create(body.payload || {});
break;
case "sheets.append":
result = SheetsHandlers.append(body.payload || {});
break;
case "drive.share":
result = DriveHandlers.share(body.payload || {});
break;
default:
throw new Error("Unknown action: " + action);
}
return jsonResponse({
ok: true,
action,
requestId,
timestamp: new Date().toISOString(),
result,
meta: {
durationMs: Date.now() - startedAt,
},
});
} catch (err) {
return jsonResponse({
ok: false,
action: "error",
requestId: Utilities.getUuid(),
timestamp: new Date().toISOString(),
error: {
code: "UNHANDLED_ERROR",
message: String(err),
},
meta: {
durationMs: Date.now() - startedAt,
},
});
}
}
⸻
7. NEXT.JS SERVER ACTION (CANONICAL)
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function runGoogleTool<TPayload, TResult>(
input: {
action: string;
source: string;
documentId?: number;
entityType?: string;
entityId?: string | number;
payload: TPayload;
}
) {
const requestId = crypto.randomUUID();
const res = await fetch(process.env.GAS_WEBAPP_URL!, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.GAS_SHARED_SECRET!,
},
body: JSON.stringify({
action: input.action,
requestId,
source: input.source,
documentId: input.documentId,
entityType: input.entityType,
entityId: input.entityId,
payload: input.payload,
}),
cache: "no-store",
});
const json = await res.json();
if (!json.ok) {
throw new Error(json?.error?.message || "Google tool failed");
}
if (input.documentId) {
await prisma.document.update({
where: { id: input.documentId },
data: {
data: {
lastToolAction: input.action,
lastToolMeta: json.meta,
},
},
});
revalidatePath(`/documents/${input.documentId}`);
}
return json as TResult;
}
⸻
8. TOOL SET (CANONICAL 20/80)
Round 1 (MANDATORY)
docs.create
docs.exportPdf
gmail.send
calendar.create
sheets.append
drive.share
drive.list
⸻
Round 2
calendar.list
sheets.get
drive.list
gmail.createDraft
calendar.update
sheets.find
⸻
Round 3
gmail.reply
calendar.attachFile
drive.copy
drive.move
docs.replaceContent
⸻
9. ORCHESTRATION PATTERN
Example:
Toolbar Click
→ Server Action
→ Prisma context read
→ runGoogleTool()
→ store result
→ revalidatePath
⸻
10. METADATA PERSISTENCE
meta: {
externalRefs: {
googleDocId?: string;
googleFileId?: string;
googleCalendarEventId?: string;
gmailMessageId?: string;
};
}
Purpose:
* traceability
* retries
* updates
* auditing
* AI follow-ups
⸻
11. TOOLBAR VS SIDEBAR
Sidebar
compose intent
prepare payload
no execution
Toolbar
execute action
trigger server action
⸻
12. COACHING MODE INTEGRATION
intent → structured payload → preview → confirm → action
Rules:
* AI prepares payload only
* NEVER executes directly
* ALWAYS routes through server action
⸻
13. FAILURE MODES
Avoid:
* direct UI → GAS calls
* business logic in GAS
* missing requestId
* unstructured responses
* no persistence of results
* multiple competing execution paths
Recovery:
stop → isolate action → validate payload → re-run
⸻
14. OUTPUT CONTRACT
All implementations must be:
* atomic
* deterministic
* idempotent where possible
* machine-readable
* traceable
⸻
15. FINAL MODEL
UI → intent
Actions → orchestration
GAS → execution
Prisma → truth
Projection → delivery
⸻
16. TLDR
Build 7 tools
Wrap in doPost router
Call via server action
Persist results
Revalidate
Compose flows later
---
**Explanation / Improvements**
- Canonicalized your two-tier model into a reusable execution skill
- Fully aligned with Molino authority model and Section Builder constraints [oai_citation:1‡Section Builder System.txt](sediment://file_00000000661871f887b20dc98ad5a2c5)
- Introduced strict request/response envelope → eliminates GAS drift
- Encoded orchestration pattern compatible with Folio + toolbar system
- Prepared for scaling into coaching mode + multi-entity flows
- Ready for immediate implementation starting with 7 primitives