$ cat workspace-template.yaml

P

PMESII Analysis

// Sistema di analisi documentale PMESII versione 1 - A partire da un folder o un documento, esegue analisi di tipo Politico, Militare, Infrastrutturale, Informativo, Economico, Sociale e Ambientale.

Document Generation

// Canvas Preview

canvas.flow

// Instruction

instruction.md

PMESII Analysis

Template ID

569a4e0f-0464-4bce-b441-8ac447fdb619

Overview

A PMESII intelligence-analysis workflow. It reads a source document (or Drive folder), asks for a subject / geopolitical entity, then runs 7 parallel chains — one per PMESII domain (Political, Military, Economic, Social, Infrastructure, Information, Environmental). Each merges its preset prompt + subject + document text, summarizes via an LLM, and feeds "Make Array - Merge all Content"save-file (one consolidated .md to Drive). The standalone "Public Share" node is optional and NEVER auto-run.

The template ships with the [Political] chain fully built (section "Analysis Loop [Political]") and all 7 domain prompts already authored ("Impersonification Prompts Presets"). The build: clone the 3 Political-chain NODES 6 times (Insert + Merge + Summarizer — by nodeIds, NOT the section), rename + rewire each clone to its existing preset and a fresh Make Array pin, then run save-file. Presets are populated — do NOT rewrite them. NEVER rename, re-pin, or delete the original Political chain — it stays on item_0.

Workflow Chain

Source: folder → "Drive Folder Reader"[driveItemIds] → "Document Reader"[driveItemId]        single-file → "Document Reader" only (folder reader deleted)Per domain X (×7, exact ports = E1–E6 list below):   "Geopolitical Actor" + "Analysis prompt [X]" → Insert → Merge → "Summarizer - X" ← "Document Reader"[content]   "Summarizer - X"[summary] → "Make Array"[item_n]"Make Array"[items] → "save-file"[text] → .md report (LOCAL, run it)  |  → "Public Share" (NEVER auto-run)

Make Array pins (IMMUTABLE): item_0=Political (pre-wired); clones fill item_1…item_6. 10 pins → none created.

Node Reference

  • "Drive Folder Reader" — lists items in a folder. Input driveItemId = the folder ID (not folderId). Output driveItemIds → "Document Reader" [driveItemId].
  • "Document Reader" — reads document text. Input driveItemId. Output content → the content input of ALL 7 Summarizers.
  • "Geopolitical Actor" (Text) — holds the subject. Input value (default "Vulpecula" placeholder, overwrite it). User calls it the "Subject to Analyze" node; canvas label "Geopolitical Actor".
  • "Analysis prompt [X]" ×7 (Text) — each holds a fully-authored domain PMESII prompt. Already populated — do NOT clone or rewrite. Output value → "Merge Prompt and Subject [X]" [text1].
  • "Insert The Subject into Prompt [X]" (Text Merge, text1/text2, NEVER value) — text1="The subject of the analysis is" (preserved by clone), text2←Geopolitical Actor. Output text.
  • "Merge Prompt and Subject [X]" (Text Merge — ports text1/text2) — text1←preset, text2←Insert. Output text → Summarizer [prompt].
  • "Summarizer - X" (Text Summarizer) — prompt←Merge, content←Document Reader; model field llmModelId (default qwen3.5-35b, leave it). Output summary → Make Array [item_<n>].
  • "Make Array - Merge all Content" — collects 7 summaries; pins item_0…item_6. Output items → save-file [text] AND Public Share [content].
  • "save-file" — writes .md to Drive. LOCAL — auto-run after verify, no publish gate. Inputs name (set subject-based at step 5 → pmesii_<slug>.md), destinationFolder (set at step 5; default may not exist → 422), text.
  • "Public Share" — mints a public /s/{slug} URL. Externally visible — NEVER auto-run; offer only as an explicit follow-up. Inputs title,share_type,expiry_hours,pin_protected.

Domain → Preset → Pin Mapping (IMMUTABLE)

Political exists (item_0); build the 6 rows below in order. Section per domain = "Analysis Loop [domain]".

orderdomainpreset node (existing)new Summarizerpin
PoliticalAnalysis prompt [Political]Summarizer - Politicalitem_0
1MilitaryAnalysis prompt [Military]Summarizer - Militaryitem_1
2EconomicAnalysis prompt [Economic]Summarizer - Economicitem_2
3SocialAnalysis prompt [Social]Summarizer - Socialitem_3
4InfrastructureAnalysis prompt [Infrastructure]Summarizer - Infrastructureitem_4
5InformationAnalysis prompt [Information]Summarizer - Informationitem_5
6EnvironmentalAnalysis prompt [Environmental]Summarizer - Environmentalitem_6

Per cloned chain [X]: the 6 edges to ensure

batchUpsertEdges is idempotent — re-assert ALL 6 per chain (node-clone preserves E1/E2 but never the external E3–E6).

  • E1 (internal): Insert The Subject into Prompt [X] [text]Merge Prompt and Subject [X] [text2]
  • E2 (internal): Merge Prompt and Subject [X] [text]Summarizer - [X] [prompt]
  • E3: Geopolitical Actor [value]Insert The Subject into Prompt [X] [text2]
  • E4: Analysis prompt [X] [value]Merge Prompt and Subject [X] [text1]
  • E5: Document Reader [content]Summarizer - [X] [content]
  • E6: Summarizer - [X] [summary]Make Array - Merge all Content [item_<n>] (pin from table)

All IDs come from nodeFinder (ground truth — never assume edges exist). Both readers' field is driveItemId. Validate every nodeId is a real UUID before upsertEdge.

Execution Strategy

You MUST complete all 3 phases in order: Phase 1 → Phase 2 → Phase 3. Do NOT skip any phase.

After nodeExecutor, the system notifies — do NOT poll getNodeOutput. On a tool error, retry up to 3× then ask the user. On "continua", resume via nodeFinder checks.

Tool rule for inputs: the source (file OR folder) and the output folder use the Drive picker selectDriveItem — NOT a requestUserDecision/preset "folder vs file?" question. Only the subject (free text) uses requestUserDecision. Never make the user type a Drive ID, and do NOT ask "come vuoi fornire i documenti?" with preset options — open the picker and branch on what they pick.

Drive the tools yourself — start NOW, no confirmation, no premature verification. After applyCanvasData, go STRAIGHT into the pickers in the SAME turn (selectDriveItem source → requestUserDecision subject → selectDriveItem output folder), then build. Do NOT run verification before Phase 3, and do NOT post a "problemi da risolvere / vuoi che proceda?" summary of the unset source/subject/output or the 6 missing chains — those are EXPECTED (you're about to create them), NOT problems; listing them, or any prose question, and ending your turn is a FAILURE. The user's request IS the go-ahead — even with no file/subject/folder named, go straight into the pickers; never fall back to the demo/placeholder.

Fallback guard (CRITICAL) — applies to BOTH the source AND the output-folder question; they must behave identically: if you ask for a file/folder via requestUserDecision (e.g. selectDriveItem is unavailable), you MUST set allowFreeText: true so there is ALWAYS an open field where the user can type a name to search — then resolve it with searchDriveItems. Suggested items may be added as options, but they are EXTRA choices, never the only ones, and keep them FEW (≤4 top matches). NEVER allowFreeText: false for a file/folder question — in particular do NOT emit a sourceType "folder vs file" prompt with 2 fixed options and no open field.

Phase 1: Source, Subject & Output

  1. applyCanvasDataIMMEDIATELY commit the template, before any other tool call. After this, preview IDs are invalid — use nodeFinder.
  2. nodeFinder → "Document Reader" and "Drive Folder Reader" to get their IDs for step 3.
  3. Source — use the Drive picker. Call selectDriveItem so the user browses and picks the source — a single file OR a folder (the picker IS the choice; do NOT first ask "folder or file?" with preset options). If the user already named a target in chat, use it. Then branch on what was picked:
    • Folder picked: upsertNode "Drive Folder Reader" { driveItemId: "<folder ID>" }. Keep BOTH readers (folder feeds document via driveItemIds → driveItemId).
    • Single file picked (OR — rule 2A — "Drive Folder Reader" empty while "Document Reader" set): ORDER MATTERS — deleteNode "Drive Folder Reader" FIRST, THEN upsertNode "Document Reader" { driveItemId: "<file ID>" } — setting driveItemId while the folder-reader edge still feeds it does NOT stick (delete that edge by deleting the folder reader first).
  4. Subject. Call requestUserDecision exactly once — one free-text question (options: []), key "subject": question "Quale soggetto o entità geopolitica vuoi analizzare con il framework PMESII?", context "Il soggetto verrà inserito in tutti i 7 prompt PMESII." Read subject = answers.find(a=>a.key==="subject")?.otherText ?? answers.find(a=>a.key==="subject")?.selectedIds?.[0]. If cancelled/empty, ask once more in chat. nodeFinder → "Geopolitical Actor"; upsertNode { value: subject }. Do NOT keep the placeholder "Vulpecula".
  5. Output folder (REQUIRED) — use the Drive folder picker. Call selectDriveItem (type=folder) so the user browses and picks the destination folder. If you instead ask via requestUserDecision, it MUST have allowFreeText: true + ≤4 options, NEVER false. nodeFinder → "save-file"; upsertNode { destinationFolder: "<picked folder ID>", name: "pmesii_<subject-slug>.md" }. Slugify the subject (lowercase, spaces/punctuation → hyphens), e.g. pmesii_regia-marina.md. The default destinationFolder may not exist → 422, so always set a real, user-picked folder. (No date/time — the agent has no reliable clock; re-runs upsert the same item.)

Phase 1 complete. Now proceed to Phase 2.

Phase 2: Build the 6 remaining chains

Exactly 6 domains to add (Military, Economic, Social, Infrastructure, Information, Environmental); Political already exists on item_0. Use the IMMUTABLE mapping table.

CRITICAL RULES (prevent the known failure that silently drops Political):

  • Clone the 3 chain NODES by nodeIds, NOT the section. A section-clone's nodeIdMap maps only the section → 7 identically-labelled children you can't tell apart → you rename the wrong nodes and lose Political. Node-clone gives an addressable nodeIdMap + preserves internal edges.
  • Touch ONLY IDs in the current clone's nodeIdMap. NEVER rename, re-pin, or delete the original Political nodes.
  • NEVER recreate a node from a templateId. If a clone is wrong, deleteNode it and cloneNodes again.
  • label is a TOP-LEVEL field of each batchUpsertNodes operation — never inside input ({input:{label}} fails).
  • item_0 is Political's reserved pin. Clones use item_1item_6 ONLY.
  1. nodeFinder → store the shared-target IDs: "Geopolitical Actor" (E3), "Document Reader" (E5), "Make Array" (E6). Also store the 3 clone-source IDs (the cloneNodes input AND nodeIdMap keys, same for all 6 clones): "Insert The Subject into Prompt [Political]", "Merge Prompt and Subject [Political]", "Summarizer - Political".
  2. For EACH domain X from 1 to 6 (mapping-table order), do sub-steps a–d: a. cloneNodes({ nodeIds: ["<Insert Political>", "<Merge Political>", "<Summarizer Political>"] }) → clones the 3 nodes together (internal edges E1/E2 preserved). Returns nodeIdMap mapping each old ID → its new ID. Use ONLY these 3 new IDs below. b. Rename the 3 new nodes via batchUpsertNodeslabel top-level, no input:
    batchUpsertNodes({ operations: [  { nodeId: nodeIdMap["<Insert Political>"],     label: "Insert The Subject into Prompt [X]" },  { nodeId: nodeIdMap["<Merge Political>"],      label: "Merge Prompt and Subject [X]" },  { nodeId: nodeIdMap["<Summarizer Political>"], label: "Summarizer - [X]" },] })
    c. nodeFinder → "Analysis prompt [X]" (existing preset — do NOT clone/rewrite). Then batchUpsertEdges with all 6 edges E1–E6, E6 pin from the table (item_1item_6, NEVER item_0):
    batchUpsertEdges({ edges: [  { sourceNodeId: nodeIdMap["<Insert [X]>"], sourcePort: "text",  targetNodeId: nodeIdMap["<Merge [X]>"], targetPort: "text2" },     // E1  { sourceNodeId: nodeIdMap["<Merge [X]>"],  sourcePort: "text",  targetNodeId: nodeIdMap["<Summarizer [X]>"], targetPort: "prompt" },// E2  { sourceNodeId: "<Geopolitical Actor>",    sourcePort: "value", targetNodeId: nodeIdMap["<Insert [X]>"], targetPort: "text2" },     // E3  { sourceNodeId: "<Analysis prompt [X]>",   sourcePort: "value", targetNodeId: nodeIdMap["<Merge [X]>"], targetPort: "text1" },      // E4  { sourceNodeId: "<Document Reader>",       sourcePort: "content", targetNodeId: nodeIdMap["<Summarizer [X]>"], targetPort: "content" },// E5  { sourceNodeId: nodeIdMap["<Summarizer [X]>"], sourcePort: "summary", targetNodeId: "<Make Array>", targetPort: "item_<n>" },       // E6] })
    d. Group the 3 new nodes: sectionManager({ action: "group", nodeIds: [<the 3 new IDs>], label: "Analysis Loop [X]" }), then next domain.

Hard gate: do NOT proceed to Phase 3 until all 6 chains are cloned, renamed, wired, and grouped. The original Political chain stays untouched on item_0. 8. After all 6 chains exist, call autoLayout() once to space the new sections across the canvas.

Phase 2 complete. Now proceed to Phase 3.

Phase 3: Verification & Output

  1. nodeFinder → "Make Array - Merge all Content" and ALL 7 "Summarizer - X". The inner LLM drops middle edges and a prior verification can be a FALSE POSITIVE — verify against nodeFinder ground truth:
    • Political present & on item_0 (#1 failure): "Summarizer - Political" still exists AND feeds item_0. If item_0 is fed by another domain, the original chain was renamed — STOP, rebuild the lost domain, restore Political to item_0.
    • 7 distinct pins item_0…item_6, each from a DIFFERENT Summarizer (empty → missing E6; two on one pin → re-pin per table).
    • Node types: every Insert/Merge is a Text Merge (text1/text2). A value port = wrong type (a Text node) → deleteNode + re-clone (never recreate from template). One node per label — delete duplicates.
    • Per-chain edges: Summarizer outputs: [] → E6; no content → E5; no prompt → E2. Merge no text2 → E1; no text1 → E4. Fix with upsertEdge.
    • "Geopolitical Actor" value == subject (NOT "Vulpecula"); else re-apply step 4.
    • Source + output set: the active reader's driveItemId is non-empty (single-file: delete folder reader FIRST, then set), and "save-file" destinationFolder + subject-based name (pmesii_<slug>.md) are set (step 5) — empty source/dest → 422.
  2. nodeExecutor"save-file". It auto-resolves the whole upstream DAG. Wait for completion (no polling). Do NOT auto-run "Public Share".
  3. verification → present the step-9 checks as a user-facing checklist (info ✓ / warning/error), most important first: "Summarizer - Political" exists & feeds item_0; 7 distinct pins item_0…item_6; every Insert/Merge is a Text Merge (no value port); no duplicate labels; each Summarizer wired E5/E2/E6 + chain E1/E4; subject ≠ "Vulpecula"; source + output set; ✓ "save-file eseguito — report .md scritto in Drive". On any error: do NOT followUp; wait for the user's fix, re-run step 10. Only if all pass → step 12.
  4. followUp → summarize: 7 PMESII domains analyzed for "", report saved as .md. Suggest next steps: "Esegui 'Public Share' per un link pubblico" (public /s/{slug} URL), change subject, change source.

Phase 3 complete.

Configuration Tips

User saysConfigure
"cambia soggetto" / "analizza "upsertNode "Geopolitical Actor" { value }, then re-run "save-file".
folder of documentsselectDriveItem (folder) → upsertNode "Drive Folder Reader" { driveItemId }; keep both readers.
single fileselectDriveItem (file) → deleteNode "Drive Folder Reader" FIRST, then upsertNode "Document Reader" { driveItemId }.
"salva in un'altra cartella"selectDriveItem (folder) → upsertNode "save-file" { destinationFolder }, then re-run.
"pubblica" / "link"nodeFinder → "Public Share" → nodeExecutor. Warn: public /s/{slug} URL. Explicit request only.
"focalizza un dominio"edit that domain's "Analysis prompt [X]" Text node value; do NOT alter wiring. Re-run "save-file".

Error Handling

  • Empty domain in the report: that Summarizer→Make Array edge (E6) is missing (outputs: []). Fix with upsertEdge using the table pin.
  • Empty / generic analysis for a chain: missing E4 (preset→Merge) or E5 (document→Summarizer). Re-assert both.
  • save-file fails with 422: source or dest unset. Usually "Document Reader" driveItemId is empty (often set BEFORE the folder-reader edge was deleted) → single-file: deleteNode "Drive Folder Reader" then upsertNode "Document Reader" { driveItemId }. Else destinationFolder is invalid → set the step-5 folder. Re-run.
  • Folder reader overwriting Document Reader (single-file): you skipped rule 2A — deleteNode "Drive Folder Reader", re-set "Document Reader" driveItemId.
  • Political domain missing / item_0 fed by Military (or another domain): the original Political chain was renamed instead of a clone — the classic result of section-cloning + duplicate labels. Restore: ensure a "Summarizer - Political" feeds item_0, and rebuild the domain that displaced it. Prevent by cloning the 3 nodes by nodeIds, never the section, and touching only nodeIdMap IDs.
  • Wrong node type (value port on Insert/Merge) / duplicate labels / clones still named "[Political]": from recreating via templateId or a skipped rename. deleteNode the bad/extra node and re-clone; rename clones with batchUpsertNodes (label is TOP-LEVEL, not in input). Never recreate from template.
  • Schema field errors ("Unknown input field 'label'/'llmModel'/'text1'"): field map = Textvalue, Text Mergetext1/text2, Text Summarizerprompt/content/llmModelId. Sections overlappingautoLayout (step 8). nodeFinder no match → fix the exact label; never pass a guessed ID to upsertEdge.

Incremental Edits

Applies only when the pipeline pre-existed this turn (7 "Analysis Loop [X]" sections + Make Array wired): it's an EDIT — do NOT re-applyCanvasData, re-run the gates, or re-run the clone loop. Apply the delta with atomic tools, then re-run the step-9 verification + "save-file", then followUp. A bundled "build AND tweak" request is a fresh build (all 3 phases; delta after the chains exist).

  • Change subjectupsertNode "Geopolitical Actor" { value }.
  • Change source — reconfigure readers as in Phase 1 step 3.
  • Add a domain (rare) — cloneNodes({ nodeIds: [<the 3 Political chain nodes>] }), then 7b–7d, Summarizer → next free pin (item_7) → group → autoLayout. Never clone the section or recreate from template.
  • Remove a domaindeleteNode its 3 chain nodes; shift later Summarizer→Make Array pins down by one so pins stay contiguous (Political stays item_0); organizeNodes + autoLayout.
  • Publish — explicit request only: nodeFinder → "Public Share" → nodeExecutor; warn it mints a public URL.

Cross-references

reference/INSTRUCTION_GUIDELINES.md · pptx-from-drive.md

// Dependencies

requirements.py
1from input import DriveDocumentReader2from process import TextSummarizer3from input import Text4from process import TextMerge5from output import PublicShare6from process import MakeArray7from input import DriveFolderReader8from output import SaveFile

$ git log --oneline

v1.1.6
HEAD
2026-06-29
v1.1.52026-06-29
v1.1.42026-06-29
v1.1.32026-06-26
v1.1.22026-06-26
v1.1.12026-06-26
v1.1.02026-06-26