$ cat workspace-template.yaml

P

Presentazione PPTX dalla Libreria Layout

// Crea una presentazione PowerPoint sul master del cliente dai documenti di una cartella Drive (o da un singolo file). Il nodo Libreria Layout legge la libreria di layout che il curatore ha pubblicato con un nodo Brand Master; l'agente sceglie il layout di ogni slide dalla libreria, scrive i contenuti, e il PPTX Builder compila il master vero, con anteprime PNG; il PDF Builder ne fa anche un PDF da condividere o stampare. Se il workspace ha già il suo Brand Master, usa quello (modalità locale). Niente viene pubblicato fuori dal workspace. Builds a PowerPoint deck on the customer's own master from the documents in a Drive folder (or one file). The Layout Library node reads the layout library a curator published with a Brand Master node; the agent picks every slide's layout from that library, writes the content, and the PPTX Builder fills the real master, with PNG previews; the PDF Builder also turns it into a PDF to share or print. A workspace with its own Brand Master uses that one (local mode). Nothing is published outside the workspace.

Document Generation
#pptx#pdf#powerpoint#presentazione#presentation#slides#master#brand#layout#libreria#library#drive#documenti

// Canvas Preview

canvas.flow

// Instruction

instruction.md

Presentazione PPTX dalla Libreria Layout del Cliente

Template ID: assigned by each marketplace when the template is published (source: fixture apps/marketplace/fixtures/workspace-templates/pptx-from-library.workspace.json, built by scripts/pptx-from-library/build_template.py).

Builds a PowerPoint deck on the customer's OWN master from the documents in a Drive folder (or one Drive file). "Libreria Layout" (Layout Library) reads the layout library a curator published with a Brand Master node in another workspace; in local mode (this workspace has its own "Brand Master") that Brand Master is the library instead. You plan every slide and pick its layout from that library by the layouts' descriptions and slot counts; "Contenuto Presentazione" writes all slide content in one pass; ONE expandPrototype call clones the slide prototype per slide and wires each clone to BOTH the content and the library (a feed ARRAY); Make Array → Presentation Composer (library + backgrounds) → PPTX Builder (presentation + library + master) produces the .pptx, and the PDF Builder turns that .pptx into a .pdf. Nothing here writes outside the workspace, so both run at the end without asking.

Units. Page = slide. A deck of P slides = 1 cover + P − 2 content slides + 1 closing, all clones of "Slide #x". When the user states a size ("una lezione di 12 slide"), P is the TOTAL.

"Seleziona Materiale Sorgente" (folder) → "Leggi i Documenti disponibili" → "Selezione dei Topic Principali""Libreria Layout" (curator's library workspace) ──library──┐"Contenuto Presentazione" ──summary──────────────────────┐ │  → per slide n = 1…P: "Slide #n" (clone of "Slide #x", layout from the library)  → "Make Array" (item_0 = cover … item_<P−1> = closing)    → "Presentation Composer" (+ library, backgrounds) → "PPTX Builder" (+ library, master) → .pptx      → "PDF Builder" → .pdf

You MUST complete all 5 phases in order: Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5. Do NOT skip any phase. When this instruction reaches you as a section index, load each phase's section before entering it, starting with loadInstructionSection({ sectionId: "phase-1-setup" }). NEVER set llmModelId on any node: leave it empty.

Overview

A lecturer (or any author) asks for a deck "on our template". The deck must use the customer's real PowerPoint master, so its slides are NOT the built-in template_name layouts: each Slide Generator runs in library mode — its library input is wired to the library node (libraryNodeId: "Libreria Layout", or "Brand Master" in local mode) and its layout input names one layout of that library (a layoutId, the layout's name in the master). There is no automatic layout in library mode: you choose every layout while planning, from the list the library node returns in its layouts output (step 7). The Slide Generator then writes slot JSON that must fit the layout's slots; text that does not fit fails the slide after 3 attempts, so pick layouts whose slots fit the content. The result is a .pptx file built by the PPTX Builder on the master itself, plus PNG previews, and the same deck as a .pdf from the PDF Builder.

Workflow Chain

[A folder] "Seleziona Materiale Sorgente" (Drive Folder Reader) → "Leggi i Documenti disponibili" (Drive Content Reader)[B single file] "Leggi i Documenti disponibili" only (delete the folder reader)  → "Selezione dei Topic Principali" (one topic per content slide)  → "Contenuto Presentazione" (ALL slide content, one pass, shaped to the planned layouts)
"Libreria Layout" (Layout Library, workspaceId = the curator's library workspace)  ├─ library ──────→ every "Slide #n" .library   (feed[1] of expandPrototype)  ├─ library ──────→ "Presentation Composer".library and "PPTX Builder".library  ├─ backgrounds ──→ "Presentation Composer".backgrounds  └─ master ───────→ "PPTX Builder".master
Local mode: this workspace's "Brand Master" replaces "Libreria Layout" in every edge above (same output names: library, backgrounds, master); "Libreria Layout" is deleted at step 5e.
"Contenuto Presentazione".summary → every "Slide #n" .content   (feed[0] of expandPrototype)"Slide #n".slide_html → "Make Array".item_<n−1>"Make Array".items → "Presentation Composer".slides → .presentation → "PPTX Builder".presentation → pptx"PPTX Builder".pptx → "PDF Builder".pptx → pdf

Node Reference

Canvas labels are Italian; match them exactly with nodeFinder. After applyCanvasData the nodesPreview ids are dead: re-resolve every label. Port lists and defaults are in the schema reference in <current_workspace>.

  • "Seleziona Materiale Sorgente" (Drive Folder Reader) — folder mode only; driveItemId takes the FOLDER id; output driveItemIds.
  • "Leggi i Documenti disponibili" (Drive Content Reader) — driveItemId → content, feeding "Selezione dei Topic Principali" and "Contenuto Presentazione".
  • "Libreria Layout" (Layout Library) — input workspaceId (the workspace whose Brand Master publishes the library). Outputs library (the handle: enabled layouts, slots, capacities, library CSS), master (File), backgrounds (Image[]). Run it again to pick up the curator's latest publication.
  • "Brand Master" (local mode only; not in the template) — the curator's node: driveItemId (the master) and curation belong to the curator, NEVER write them. Outputs library, master, backgrounds (same names and meaning as "Libreria Layout"; never wire catalog or thumbnails). Running it publishes the master; it is a cache hit when nothing changed since its last run.
  • "Selezione dei Topic Principali" (Topic Selector, content → summary) — configure prompt. Leave llmModelId EMPTY.
  • "Contenuto Presentazione" (Content Generator, content → summary) — configure prompt (rewritten at step 12). Leave llmModelId EMPTY.
  • "Slide #x" (Slide Generator, prototype) — standalone, not in a section. Inputs content, instruction, library (LayoutLibrary), layout (widget library-layout-picker: a layoutId of the wired library), language. template_name is ignored in library mode. Output slide_html. NEVER execute or delete the prototype, and never wire it to "Make Array".
  • "Make Array" — collector; pin order = slide order, item_0 = cover. Ships 10 pins; expandPrototype grows the pins its items need.
  • "Presentation Composer" (slides → presentation) — inputs slides, title, library, backgrounds. With library wired, its theme follows the library: do NOT set theme. It fails a slide that is not from this library (FOREIGN_SECTION).
  • "PPTX Builder" — inputs presentation, library, master; outputs pptx (File), previews (Image[]), previews_error (String). It checks every slide against the library and fails listing every problem by slide and slot. It writes nothing outside the workspace.
  • "PDF Builder" — inputs presentation (Html) and pptx (File), ONE of them wired: here pptx ← "PPTX Builder".pptx, so the PDF matches the PowerPoint file, and presentation stays empty (both wired fails the node). Output pdf (File, named after the .pptx), one page per slide. LibreOffice draws it, as it draws the previews: fonts can differ from PowerPoint, and hidden slides are left out. Executing it runs the PPTX Builder first. It writes nothing outside the workspace.

Execution Strategy

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

Phase 1 = steps 1–5 · Phase 2 = 6–9 · Phase 3 = 10–12 · Phase 4 = 13–15 · Phase 5 = 16–20. Numbering is continuous; letter-suffixed steps are mandatory sub-steps.

Gates: Phase 1 ends with ONE requestUserDecision (only for what the message left open); Phases 2–4 run without pauses; Phase 5 audits, then executes "PDF Builder", which builds the .pptx and the .pdf (file-only outputs: no publish question).

Global rules:

  • After nodeExecutor the system notifies on completion — do NOT poll getNodeOutput in a loop.
  • On a tool error retry up to 3 times with corrected parameters; then ask the user. Never skip a step.
  • applyCanvasData is used once, at step 1. Later changes use expandPrototype or atomic tools (upsertNode, upsertEdge, batchUpsertNodes, batchUpsertEdges, deleteNode, deleteEdge).
  • followUp is called only at step 20.

Phase 1: Setup

Gate keys: slideCount, library. Both must be resolved by the end of step 5.

  1. applyCanvasData → IMMEDIATELY commit the template, before any other tool call.
  2. Find the source: an attached Drive chip or @mention wins; otherwise searchDriveItems with the name the user gave. 0 results → one shorter query; still nothing after 2 attempts → ask the user for the item. Never retry the same query.
  3. Configure the data source: if folder — nodeFinder → "Seleziona Materiale Sorgente", upsertNode({ nodeId: "<found ID>", input: { driveItemId: "<folder ID>" } }). If single file — nodeFinder → "Seleziona Materiale Sorgente", deleteNode it, then nodeFinder → "Leggi i Documenti disponibili" and upsertNode the same driveItemId there.
  4. Find the library candidates (no write yet). nodeFinder({ queries: ["Brand Master", "Libreria Layout"], includeConfig: true }). Local mode — a "Brand Master" is on this canvas: libraryNodeId = its id, known.library = "local", keep the "Libreria Layout" id as unusedLibraryId, and skip the rest of this step. Otherwise libraryNodeId = the "Libreria Layout" id. If its workspaceId is already set (an admin pre-configured the template), known.library = <that id> and skip the rest of this step. Otherwise: the message names a library ("con la libreria CASD 2026") → listWorkspaces({ query: "<that name>" }); exactly one match → known.library = <its id>. No name, or 0 or several matches → listWorkspaces({}) and keep up to 6 candidates whose names suggest a library ("Libreria", "Library", "Master", "Template", the customer's name) as libraryCandidates. Never pick a library yourself among several.
  5. Phase 1 gate. Do 5a, 5b, 5c, then 5d (and 5e in local mode).

5a — extract what the ORIGINAL message already says. Set known.slideCount from an explicit integer deck size ("12 slide", "a 12-slide deck", "12 页") — it is the TOTAL P, cover and closing included. Vague sizes ("una lezione breve", "circa 10") are NOT known. Print one line naming what you extracted and what you will still ask.

5b — ask ONLY what is still null, in ONE call. Never call requestUserDecision more than once in Phase 1; if nothing is missing, do not call it.

requestUserDecision({  questions: [    { key: "slideCount", question: "Quante slide deve avere la presentazione, copertina e chiusura comprese?", header: "Slide",      context: "Ogni slide usa un layout della libreria del cliente.",      options: [ { id: "8", label: "8 slide" }, { id: "12", label: "12 slide", recommended: true }, { id: "16", label: "16 slide" }, { id: "auto", label: "Automatico", description: "In base ai documenti, al massimo 15" } ] },    { key: "library", question: "Quale libreria di layout devo usare?", header: "Libreria",      context: "La libreria è il workspace in cui il curatore ha pubblicato il master PowerPoint con il nodo Brand Master.",      options: [ /* one { id: "<workspace id>", label: "<workspace name>" } per libraryCandidates entry */ ] }  ]})

Write the questions in the user's language. Keep a question only if its known.<key> is null.

5c — merge. slideCount ← known value / answer (otherText accepted if it is an integer 3–40) [auto]. library ← known value / the chosen option id / an otherText name resolved with listWorkspaces({ query }) to exactly one workspace. Cancelled or unresolved library → STOP: say the deck needs the customer's library and ask which workspace publishes it. Do not continue without it.

5d — write the setup in ONE batchUpsertNodes call. Resolve ids with ONE nodeFinder call (queries: "Libreria Layout", "Selezione dei Topic Principali", "Contenuto Presentazione", "Slide #x"). Ops: { nodeId: libraryNodeId, input: { workspaceId: "<library>" } } (not in local mode: the Brand Master has no workspaceId), plus, when the deck language resolves, { input: { language: "<code>" } } on the three content nodes ("Selezione dei Topic Principali", "Contenuto Presentazione", "Slide #x" — the prototype passes it to every clone). The deck language is the language the user explicitly asked for, else the language of the request message; never ask, never infer it from the documents. Codes: it, en, zh, de, es, fr, ja, ko, nl, pt, ru. Unresolvable → leave language as is (auto).

5e — local mode only. deleteNode({ nodeId: unusedLibraryId }): "Libreria Layout" goes with its edges, so step 15 wires the Composer and the PPTX Builder from the Brand Master.

Phase 1 complete. Now proceed to Phase 2. Do NOT skip to Phase 5.

Phase 2: Library and Topics

  1. nodeExecutor({ nodeId: libraryNodeId }) — "Libreria Layout" reads the curator's latest publication; in local mode the Brand Master publishes this workspace's master. Wait for completion. If it fails, read the error with getNodeOutput({ nodeId: libraryNodeId, keys: "master" }) (errorMessage carries [LIBRARY] {"code":…}), apply the matching row of ## Error Handling, tell the user, and STOP: never plan layouts without the library.
  2. Read the layouts from the library node's execution result: getNodeOutput({ nodeId: libraryNodeId, keys: "layouts" }) returns one line per enabled layout: layoutId "<id>" — label — description — slots — picture. Store it as layouts; read it again whenever it is no longer in your context or the library node has run again. No layouts: status FAILED → run the library node once more; otherwise → STOP: tell the user the layouts could not be read and the library node must be updated. Never call getNodeOutput with keys: "library" (the handle carries the full catalog and CSS) and never guess a layoutId. Classify each layout once, from its label and description: cover (copertina, cover, titolo della presentazione), closing (chiusura, conclusione, riepilogo, grazie), index (indice, agenda, sommario), section (sezione, capitolo, divisore), table (a table slot), columns (a "fill exactly 2/3" group of side-by-side columns), text (one long text slot), bullets (a bullets slot), picture (picture: yes).
  3. Configure and run the topic selector. content_slides = P − 2 when slideCount is a number. nodeFinder → "Selezione dei Topic Principali"; upsertNode({ nodeId, input: { prompt: "Seleziona i <content_slides> argomenti principali dei documenti, uno per slide, nell'ordine in cui vanno presentati. Per ciascuno indica il titolo e il tipo di contenuto (spiegazione, elenco di punti, confronto tra due elementi, dati da tabella, sequenza di fasi)." } }) — with auto, "Seleziona gli argomenti principali (al massimo 13)". Then nodeExecutor it ("Leggi i Documenti disponibili" runs as its dependency) and wait.
  4. getNodeOutput({ nodeId: "<topic selector id>", keys: "summary" }) → store topics = [name, …] with each topic's content type, in order. More than content_slides → keep the first content_slides; fewer → keep them and say how many the documents support (never invent a topic). P = topics.length + 2. ALL slide titles used later come from this output.

Phase 2 complete. Now proceed to Phase 3. Do NOT skip to Phase 5.

Phase 3: Slide Plan and Content

  1. Build the MAPPING TABLE — it is IMMUTABLE from here on. One row per slide, pin 0 … P − 1: pin 0 = cover, pins 1 … P − 2 = the topics in order, pin P − 1 = closing. For each row pick ONE layoutId from layouts by these rules, first match wins:

    • a. Cover (pin 0): the first cover layout. Never use a cover layout on any other pin.
    • b. Closing (last pin): the first closing layout; none → a bullets layout.
    • c. Index: only when the library has an index layout AND its "fill exactly N" count equals the number of parts you will list (pick N parts from the topics). Otherwise no index slide.
    • d. Content slides, by the topic's content type: data → table; two sides compared → columns (only with exactly the group's count of sides); phases or 3–6 parallel points → bullets; a definition or explanation → text; a new part of the lesson (≥10 slides) → section.
    • e. Never a layout with picture: yes: this template wires no image into the slides, and an empty picture slot leaves a hole.
    • f. Fit: a long explanation needs a text slot with a large character limit; a slide with 6 points needs ≤6 items or more. A layout that says "fill exactly N" takes exactly N items: with another count choose a table within its row limit or a text layout without a picture (rule g yields for that slide only), and never invent or drop an item to reach N.
    • g. Variety: use at least min(6, number of usable layouts) distinct layouts when P ≥ 10, and never the same layout on 3 consecutive pins.
    • h. Copy each layoutId EXACTLY as the JSON string shown (same case, spaces, +, accents); never translate or invent one.

    Print the table: pin | slide | title | layoutId | why (shape → layout).

  2. Write each row's instruction now (it goes into the clone at step 14): "Slide <n> di <P> — '<title>': <what the slide must say, from the topic>. Layout '<label>': <its slots and counts, e.g. 'titolo e 5 voci con numero, titolo e descrizione'>." For the cover: lesson title, subtitle, and the author/role only if the documents give them.
  3. nodeFinder → "Contenuto Presentazione"; upsertNode({ nodeId, input: { prompt: "Scrivi il contenuto di ogni slide della presentazione, nell'ordine:\n\n#Slide 1 (copertina): <title>\n#Slide 2: <title> — <layout label>: <slots and counts>\n…\n#Slide <P> (chiusura): <title>\n\nPer ogni slide scrivi solo i testi che il suo layout può contenere, brevi e completi, usando solo fatti dei documenti." } }) — one line per mapping-table row with the exact titles. Then nodeExecutor it immediately and wait.

Phase 3 complete. Now proceed to Phase 4. Do NOT skip to Phase 5.

Phase 4: Slide Expansion

expandPrototype replaces clone → configure → wire → pins → layout with ONE atomic call: it clones "Slide #x" once per item, applies each item's config (keyed by the PROTOTYPE id), wires every feed in the feed array into every clone, wires each clone's slide_html to item_<pin> (adding missing Make Array pins), and lays the clones out. On error nothing changed: fix the arguments and retry. Do NOT re-run batchCloneNodes, organizeNodes, autoLayout or sectionManager over its result.

  1. nodeFinder (ONE call, queries) → contentId ("Contenuto Presentazione"), libraryNodeId ("Libreria Layout", or "Brand Master" in local mode), protoId ("Slide #x"), makeArrayId ("Make Array"), composerId ("Presentation Composer"), builderId ("PPTX Builder"), pdfBuilderId ("PDF Builder").
  2. Expand — ONE call for all P rows, every item with its explicit pin from the table:
    expandPrototype({  prototype: { nodeIds: ["<protoId>"] },  items: [    { label: "Slide #1 - <title>", pin: 0, config: { "<protoId>": { layout: "<layoutId of pin 0>", instruction: "<row 1 instruction>" } } },    // …one entry per mapping-table row, pin 0 … P − 1  ],  feed: [    { fromNodeId: "<contentId>", fromHandle: "summary", toPrototypeNodeId: "<protoId>", toHandle: "content" },    { fromNodeId: "<libraryNodeId>", fromHandle: "library", toPrototypeNodeId: "<protoId>", toHandle: "library" }  ],  collector: { nodeId: "<makeArrayId>", fromPrototypeNodeId: "<protoId>", fromHandle: "slide_html" },  layout: { groupLabel: "Slide della presentazione" }})
    The feed MUST be the array of BOTH edges. A clone without the library edge runs in built-in mode and the Composer rejects its slide. Keep instances[i].nodeIdMap[protoId] = the clone id of row i.
  3. Wire the deck — check ALL seven edges, then create every missing one in ONE batchUpsertEdges call. Read the incoming edges of "Presentation Composer", "PPTX Builder" and "PDF Builder" from step 13's nodeFinder result (never assume one is there, and never re-create one that exists: a second edge into the same input is an error): makeArrayId.items → composerId.slides, libraryNodeId.library → composerId.library, libraryNodeId.backgrounds → composerId.backgrounds, composerId.presentation → builderId.presentation, libraryNodeId.library → builderId.library, libraryNodeId.master → builderId.master, builderId.pptx → pdfBuilderId.pptx. Then upsertNode({ nodeId: composerId, input: { title: "<lesson title>" } }).

Phase 4 complete. Now proceed to Phase 5.

Phase 5: Verification and Build

  1. Audit the canvas, not your plan. nodeFinder({ queries: ["Slide #", "Make Array", "Presentation Composer", "PPTX Builder", "PDF Builder"], includeConfig: true }). Run every check and fix every failure (one repair pass; then re-run the checks once):

    #Check → fix
    C1EACH "Slide #n" clone has content ← "Contenuto Presentazione".summary AND library ← the library node's library (libraryNodeId) → missing edge: upsertEdge it (inputs: [] means an empty slide)
    C2EACH clone's layout is a layoutId listed in layouts (step 7), exactly, and none has picture: yes → upsertNode the mapping-table value
    C3pin 0 uses the cover layout, the last pin the closing layout, and at least min(6, usable layouts) distinct layouts when P ≥ 10
    C4Make Array roster: pins item_0 … item_<P−1> each have exactly ONE incoming edge from the right clone, in table order, no gaps, no duplicates; the prototype "Slide #x" is NOT wired → deleteEdge the wrong edge, upsertEdge the right one. Do NOT re-run expandPrototype (it would clone a second set)
    C5"Presentation Composer" has slides, library and backgrounds wired ONCE each; "PPTX Builder" has presentation, library and master wired ONCE each; "PDF Builder" has pptx ← "PPTX Builder".pptx ONCE and nothing into presentation → create a missing edge as in step 15; deleteEdge a duplicate or a presentation edge

    Print the roster | pin | expected slide | actual source | status |. Still failing after one repair → STOP: do not execute; report each failure at step 19.

  2. nodeExecutor({ nodeId: pdfBuilderId }) — it runs every slide, the Composer, the PPTX Builder and the PDF Builder. Wait for completion. If it fails: find the failed nodes (nodeFinder({ filter: "failed" })), read each errorMessage (getNodeOutput({ nodeId, keys: "slide_html" })) and apply ## Error Handling. Only "PDF Builder" failed (the PPTX Builder completed) → the .pptx is ready: apply the PDF Builder row once, and never change a slide for it. A slide that does not fit gets ONE fix — a roomier layout of the same kind from layouts via upsertNode({ nodeId, input: { layout } }), or a shorter instruction — then run step 17 once more. Never loop.
  3. getNodeOutput({ nodeId: builderId, keys: "pptx,previews_error" }) → confirm the .pptx exists. A non-empty previews_error only means the PNG previews are missing: the file is ready. Then getNodeOutput({ nodeId: pdfBuilderId, keys: "pdf" }) → confirm the .pdf exists. 18a. Look at the slides — follow the skill Revisione Visiva (listed under <workspace_skills>: load it first, loadInstructionSection({ skill }) with its pin), with: images node "Anteprima delle Slide" (the PDF, one image per page); record "Revisione delle Slide"; image n is slide n, its row's shows the slide's title; all nine checks. The one fix allowed: upsertNode that slide's clone instruction with the concrete change (a shorter title, the number's unit, no filler item, the full address), then nodeExecutor({ nodeId: pdfBuilderId }) again. A problem the layout causes is reported. The skill is not available (the list says so) → skip this step and say the PDF is on "Anteprima delle Slide" to check.
  4. verification — one item per check C1–C5 (state after the fixes), one for the visual check of step 18a (what is still open, per slide), one for the build ("PPTX pronto: " or the failure with its slide and slot), one for previews, one for the PDF ("PDF pronto: " or why it is missing). Summary in the user's language: slides delivered (the C4 roster count, not the plan), layouts used, library name.
  5. followUp — the deck is ready to download from "PPTX Builder" (.pptx) and "PDF Builder" (.pdf); suggest 2–3 next steps (change one slide's layout, add a slide, re-run "Libreria Layout" after the curator publishes again; in local mode, change layouts in the Brand Master's "Cura layout" and Pubblica).

Incremental Edits to an Existing Deck

Detection: the clones "Slide #n" and their Make Array edges existed BEFORE this turn → this is an EDIT. Do not call applyCanvasData, do not ask the Phase-1 questions, do not re-run Phases 2–4. A message that asks to build AND tweak is a build: run all phases, then apply the tweak before step 16.

  • Change a slide's layout: pick the new layoutId from layouts (same rules as step 10; step 7 if it is gone) → upsertNode({ nodeId: "<clone id>", input: { layout: "<layoutId>" } }).
  • Add a slide before the closing: move the closing up one pin (deleteEdge closing → item_<k>, upsertEdge closing → item_<k+1>), then ONE expandPrototype with one item at pin: k and the same two-edge feed array, WITHOUT layout (the section already exists). Leave the new clone next to the section: organizeNodes + autoLayout are enough. NEVER call sectionManager(group) on nodes already in a section, and never retry it after "already in a section". Update "Contenuto Presentazione"'s prompt with the new slide and run it again.
  • Remove a slide: deleteNode its clone, then shift every later pin down by one (deleteEdge + upsertEdge each), keeping the closing last.
  • Reorder: swap the Make Array edges of the two clones.
  • Only the PDF ("rifai il PDF", or it failed at step 17 while the .pptx is ready): nodeExecutor "PDF Builder"; the slides and the PPTX Builder re-run only if they are stale.
  • New library publication: nodeExecutor "Libreria Layout" again; its slides become stale and re-run on the next build. In local mode the curator's Pubblica already re-ran the Brand Master: the slides are stale the same way. Read layouts again (step 7) and re-check C2.

Then run steps 16–20 (audit, build, verification, followUp).

Configuration Tips

User saysConfigure
"una lezione di 12 slide" / "a 12-slide deck"slideCount = 12 (total, cover and closing included) — not asked at 5b
"con la libreria CASD 2026"step 4: listWorkspaces({ query: "CASD 2026" }) → workspaceId on "Libreria Layout"
"usa il layout X per la slide 3"that clone's layout = X, only if X is in layouts (step 7); else say it is not in the library
"più varietà" / "slide tutte uguali"redo step 10 with rule g; upsertNode the clones' layout
"con immagini"not supported by this template: picture layouts stay unused; say so
"in inglese" / "in English"5d: language: "en" on the content nodes and the prototype
"il curatore ha aggiornato la libreria"nodeExecutor the library node ("Libreria Layout", or "Brand Master" in local mode), then re-check C2
the deck is built where the master is curated (a "Brand Master" on this canvas)local mode, step 4: no library question, "Libreria Layout" deleted at 5e
"rifai solo la slide 5"nodeExecutor that clone, then "PDF Builder" (it rebuilds the .pptx first)
"anche in PDF" / "mandami il PDF"nothing to configure: step 17 builds the .pptx and the .pdf
"il PDF ha caratteri diversi"LibreOffice draws the PDF as it draws the previews and substitutes a font it does not have; the .pptx opens in PowerPoint with the master's fonts. Say so; nothing to change
"cambia il tema"the theme follows the library: change the library (Phase 1 step 4) instead

Error Handling

Library errors arrive in errorMessage as [LIBRARY] {"code":"…","params":{…}}, possibly after other text.

Error code / symptomCauseFix
NO_LIBRARY_IN_WORKSPACE / LIBRARY_NOT_PUBLISHEDthe picked workspace has no Brand Master, or it never completed a runask the user for the right library workspace, or to have the curator publish it
LIBRARY_ACCESS_DENIED / OWNER_CANNOT_READ_LIBRARYthe library workspace is not shared with the user (or the deck owner)ask the curator to share the library workspace; then re-run "Libreria Layout"
DUPLICATE_BRAND_MASTERthe library workspace has two Brand Master nodesthe curator must keep one
LIBRARY_SERVICE_UNAVAILABLE / RENDER_SERVICE_UNAVAILABLEa service is downwait and run the node once more; then report
LAYOUT_REQUIREDa clone has no layoutset it from the mapping table (C2)
LAYOUT_NOT_IN_LIBRARYthe layoutId is misspelled or the curator turned the layout offpick a listed layout of the same kind (C2)
SLOT_OVER_CAPACITYthe slide's text did not fit after 3 attempts; params.violations names the slot and limitstep 17's one fix: a roomier layout of the same kind, or a shorter instruction
REQUIRED_SLOT_MISSING / GROUP_COUNT_MISMATCH / GROUP_GAPthe content lacked something the layout needs, or the item count did not match "fill exactly N"a layout whose counts match the content, or an instruction naming the exact count
SLOT_JSON_INVALIDthe model never answered valid slot JSONrun that clone once more; then report
FOREIGN_SECTION (Composer)a slide came from no library or another librarythe clone lacks its library edge (C1)
BACKGROUNDS_NOT_CONNECTED (Composer)library is wired but backgrounds is notwire libraryNodeId.backgrounds → composerId.backgrounds (C5), run step 17 once more
UNSUPPORTED_MASTER_FILE / NO_SUPPORTED_LAYOUTS / RENDER_FAILED / RENDER_INCOMPLETE (Brand Master, local mode)the master file is not a usable PowerPoint master, or its layouts did not rendertell the user the curator must fix the master; STOP
DECK_INVALID / UNPLACEABLE_ELEMENT (PPTX Builder)a slide does not match its layoutfix the named slide's layout (C2) and run step 17 once more
previews_error not emptypreviews could not be renderednothing to fix: the .pptx is ready
RENDER_SERVICE_UNAVAILABLE / RENDER_FAILED (PDF Builder)the PDF could not be made: the render service is down, or LibreOffice could not convert the deckthe .pptx is ready: say so, run "PDF Builder" once more; still failing → report it. Never change a slide for it
no layouts in step 7the run failed, or an older library nodestep 7: run a failed node once more, else STOP and tell the user; never guess layouts

// Dependencies

requirements.py
1from input import DriveFolderReader2from input import DriveContentReader3from process import TextSummarizer4from input import LayoutLibrary5from process import SlideGenerator6from process import MakeArray7from output import PresentationComposer8from output import PPTXBuilder9from output import PDFBuilder10from process import Snapshot

// Skills

Revisione Visivamarketplace/revisione-visiva@1.2.0

$ git log --oneline

v1.3.1
HEAD
2026-11-04
v1.3.02026-11-04
v1.2.02026-11-03
v1.1.52026-11-01
v1.1.42026-11-01
v1.1.32026-10-30
v1.1.22026-10-29
v1.1.12026-10-25
v1.1.02026-10-25
v1.0.12026-10-22
v1.0.02026-10-21