<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Keo Fung | FormLM</title>
    <description>The latest articles on DEV Community by Keo Fung | FormLM (@keofung).</description>
    <link>https://dev.to/keofung</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3945809%2F0e192d77-8846-4fe8-9653-d66d2dba8cbb.png</url>
      <title>DEV Community: Keo Fung | FormLM</title>
      <link>https://dev.to/keofung</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keofung"/>
    <language>en</language>
    <item>
      <title>The Facilitator's Stack: A Checklist From Invitation to Follow-Up</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Fri, 06 Nov 2026 01:45:28 +0000</pubDate>
      <link>https://dev.to/keofung/the-facilitators-stack-a-checklist-from-invitation-to-follow-up-1gi8</link>
      <guid>https://dev.to/keofung/the-facilitators-stack-a-checklist-from-invitation-to-follow-up-1gi8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (10/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nine posts of opinions, mechanics and tool links later, here's the whole series compressed into the artifact people actually asked me for: the runbook. Print it, paste it into Notion, ignore half of it — the checklist is the point, not the compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  T-minus 2 weeks: design
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Write the outcome sentence: "By the end, participants can ___" (part 1)&lt;/li&gt;
&lt;li&gt;[ ] Write the evidence sentence: "I'll know because ___"&lt;/li&gt;
&lt;li&gt;[ ] Draft the four-beat arc with minutes: Open → Explore → Converge → Close&lt;/li&gt;
&lt;li&gt;[ ] Pick activities from folders with run sheets, props and debrief scripts — &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt;, or file your own materials in the folder shape (part 2)&lt;/li&gt;
&lt;li&gt;[ ] Pressure-test the plan with ChatGPT or Claude; keep the one-page plan in &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  T-minus 1 week: prep
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Print props (poker-spec cards, A3 posters, both manual sizes)&lt;/li&gt;
&lt;li&gt;[ ] Create the &lt;strong&gt;sign-up card&lt;/strong&gt; and &lt;strong&gt;check-in card&lt;/strong&gt; — &lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt;, printable, live status after printing (part 3)&lt;/li&gt;
&lt;li&gt;[ ] Send the invite with the card in it; if tickets are paid, the money lives in &lt;a href="https://www.eventbrite.com" rel="noopener noreferrer"&gt;Eventbrite&lt;/a&gt;, the card handles the day&lt;/li&gt;
&lt;li&gt;[ ] Build the pre/post measure — &lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;FormLM&lt;/a&gt;, from the &lt;a href="https://formlm.me/home/template/training/pre-post-training-quiz.html" rel="noopener noreferrer"&gt;pre/post-training quiz template&lt;/a&gt;, one description and the scoring is generated (part 6)&lt;/li&gt;
&lt;li&gt;[ ] Optional, if you do this monthly: let an agent do the above from the folder's SKILL.md via &lt;a href="https://workshopskill.com/en/skills.json" rel="noopener noreferrer"&gt;skills.json&lt;/a&gt;, and build the forms over FormLM's MCP server — npm package &lt;a href="https://www.npmjs.com/package/@formlm/cli" rel="noopener noreferrer"&gt;@formlm/cli&lt;/a&gt;, &lt;code&gt;npx -y @formlm/cli mcp&lt;/code&gt; (part 7)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Day before: the room
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Pre-build the remote/hybrid board if needed — &lt;a href="https://miro.com" rel="noopener noreferrer"&gt;Miro&lt;/a&gt; or &lt;a href="https://www.figma.com/figjam/" rel="noopener noreferrer"&gt;FigJam&lt;/a&gt;, frames per group, never a blank canvas (part 5)&lt;/li&gt;
&lt;li&gt;[ ] Wi-Fi card on each table — &lt;a href="https://qrcard-ai.com/en/guides/wifi-qr-code.html" rel="noopener noreferrer"&gt;free in-browser generator&lt;/a&gt;, nothing uploaded&lt;/li&gt;
&lt;li&gt;[ ] Charge everything; pack the clicker; check the projector with the presenter deck&lt;/li&gt;
&lt;li&gt;[ ] Deal tomorrow's groups in advance — &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;random group generator&lt;/a&gt;, cross-matching if the goal is introductions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Day of: run it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Pre-measure open as people arrive (under 10 minutes, scored)&lt;/li&gt;
&lt;li&gt;[ ] Open with pairs, not the big screen — &lt;a href="https://workshopskill.com/en/activities/two-truths/" rel="noopener noreferrer"&gt;Two Truths and a Lie&lt;/a&gt;, two minutes (part 4)&lt;/li&gt;
&lt;li&gt;[ ] One live-screen moment, genuinely used: &lt;a href="https://qrcard-ai.com/en/guides/trainer-live-screen.html" rel="noopener noreferrer"&gt;trainer live screen&lt;/a&gt; — counts, not names (part 4)&lt;/li&gt;
&lt;li&gt;[ ] Every group task = artifact + visible countdown + midpoint callout (part 5)&lt;/li&gt;
&lt;li&gt;[ ] Debrief scripted, not improvised — What? So what? Now what? Exit ticket at the door; anonymous wall for the unsayable (&lt;a href="https://qrcard-ai.com/en/guides/live-message-wall.html" rel="noopener noreferrer"&gt;live message wall&lt;/a&gt;) (part 8)&lt;/li&gt;
&lt;li&gt;[ ] Close with one written commitment per person&lt;/li&gt;
&lt;li&gt;[ ] Post-measure before people leave; feedback card on the table&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Within 48 hours: follow-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] One email: commitments, materials, what changes next time (part 8)&lt;/li&gt;
&lt;li&gt;[ ] Structured feedback survey out — &lt;a href="https://formlm.me/home/template/training/workshop-feedback-form.html" rel="noopener noreferrer"&gt;workshop feedback form template&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Ten minutes of margin notes on the run sheet: what ran over, what got cut, which debrief question produced silence&lt;/li&gt;
&lt;li&gt;[ ] If a client sponsored this: the per-respondent knowledge-lift report forwards itself (part 6)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Monthly: keep the program alive
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Same date every month: the client report — attendance and deltas at the top, what the group worked on, one chart, one note (&lt;a href="https://reportskills.com/en/index.html" rel="noopener noreferrer"&gt;ReportSkills&lt;/a&gt;; the &lt;a href="https://reportskills.com/en/for/trainers/index.html" rel="noopener noreferrer"&gt;trainers' section&lt;/a&gt; and the &lt;a href="https://reportskills.com/en/guides/monthly-client-report/index.html" rel="noopener noreferrer"&gt;monthly client report guide&lt;/a&gt; define the shape) (part 9)&lt;/li&gt;
&lt;li&gt;[ ] The templates hub has the starting points; paste the tables you already have, send per-recipient on a schedule:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1futbfjvkmwoyac9akni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1futbfjvkmwoyac9akni.png" alt="The ReportSkills templates hub: client report templates browsable by industry, each landing page stating the block structure and data shape the template expects." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole stack, every URL in one place
&lt;/h2&gt;

&lt;p&gt;The tools from my own shop (I'm one of the makers — full disclosure below):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;The job&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WorkshopSkill&lt;/td&gt;
&lt;td&gt;Ready-to-run activity folders: run sheet, deck, props, debrief script, pre/post measure; SKILL.md for agents&lt;/td&gt;
&lt;td&gt;&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;workshopskill.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QRCard AI&lt;/td&gt;
&lt;td&gt;Printed QR cards: sign-ups, check-ins, feedback, live screen, message wall, group generator&lt;/td&gt;
&lt;td&gt;&lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;qrcard-ai.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FormLM&lt;/td&gt;
&lt;td&gt;Scored forms and assessments; per-respondent PDF reports; open-source MCP server&lt;/td&gt;
&lt;td&gt;&lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;formlm.me&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReportSkills&lt;/td&gt;
&lt;td&gt;Recurring branded client reports from pasted tables; scheduled, per-recipient delivery&lt;/td&gt;
&lt;td&gt;&lt;a href="https://reportskills.com/en/index.html" rel="noopener noreferrer"&gt;reportskills.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The other people's tools this series leans on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;The job&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT / Claude&lt;/td&gt;
&lt;td&gt;Outcome sharpening, drafting, arc pressure-tests&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;chatgpt.com&lt;/a&gt; / &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;The one-page plan&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;notion.so&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Slides / Canva&lt;/td&gt;
&lt;td&gt;Teaching-block slides, one-pagers&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://slides.google.com" rel="noopener noreferrer"&gt;slides.google.com&lt;/a&gt; / &lt;a href="https://www.canva.com" rel="noopener noreferrer"&gt;canva.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eventbrite&lt;/td&gt;
&lt;td&gt;Paid ticketing, refunds, receipts&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.eventbrite.com" rel="noopener noreferrer"&gt;eventbrite.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calendly / Cal.com&lt;/td&gt;
&lt;td&gt;Scheduling around the session&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://calendly.com" rel="noopener noreferrer"&gt;calendly.com&lt;/a&gt; / &lt;a href="https://cal.com" rel="noopener noreferrer"&gt;cal.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mentimeter / Slido / AhaSlides&lt;/td&gt;
&lt;td&gt;Heavyweight polling when you need the features&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.mentimeter.com" rel="noopener noreferrer"&gt;mentimeter.com&lt;/a&gt; / &lt;a href="https://www.slido.com" rel="noopener noreferrer"&gt;slido.com&lt;/a&gt; / &lt;a href="https://ahaslides.com" rel="noopener noreferrer"&gt;ahaslides.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Miro / FigJam&lt;/td&gt;
&lt;td&gt;The remote room's shared artifact&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://miro.com" rel="noopener noreferrer"&gt;miro.com&lt;/a&gt; / &lt;a href="https://www.figma.com/figjam/" rel="noopener noreferrer"&gt;figjam.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom&lt;/td&gt;
&lt;td&gt;Breakout rooms for remote programs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://zoom.us" rel="noopener noreferrer"&gt;zoom.us&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Forms&lt;/td&gt;
&lt;td&gt;Free-and-manual measurement when that's enough&lt;/td&gt;
&lt;td&gt;&lt;a href="https://forms.google.com" rel="noopener noreferrer"&gt;forms.google.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Otter.ai&lt;/td&gt;
&lt;td&gt;Debrief transcripts, with consent&lt;/td&gt;
&lt;td&gt;&lt;a href="https://otter.ai" rel="noopener noreferrer"&gt;otter.ai&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Looker Studio&lt;/td&gt;
&lt;td&gt;The dashboards world, when your data lives there&lt;/td&gt;
&lt;td&gt;&lt;a href="https://lookerstudio.google.com" rel="noopener noreferrer"&gt;lookerstudio.google.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The sentence to keep
&lt;/h2&gt;

&lt;p&gt;If the whole series had to compress to one line, it's this: &lt;strong&gt;a workshop is not a deck; it's a folder, a measure and a follow-through.&lt;/strong&gt; Every tool in the tables above just makes one of those three cheaper. A folder of markers, a show of hands, and a follow-up email you actually send beats a premium stack with a missing debrief every single time.&lt;/p&gt;

&lt;p&gt;That's the series. If you run workshops, I'd genuinely like to hear which part of the pipeline eats your evenings — the fastest way to reach me is through any of the contact pages above.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Every part of this series: &lt;a href="https://workshopskill.com/en/guides/design-a-workshop/" rel="noopener noreferrer"&gt;1 — Design before the slides&lt;/a&gt; · &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;2 — The activity folder&lt;/a&gt; · &lt;a href="https://qrcard-ai.com/en/guides/how-cards-work.html" rel="noopener noreferrer"&gt;3 — Registration and check-in&lt;/a&gt; · &lt;a href="https://qrcard-ai.com/en/guides/trainer-live-screen.html" rel="noopener noreferrer"&gt;4 — Opening the room&lt;/a&gt; · &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;5 — Group work&lt;/a&gt; · &lt;a href="https://formlm.me/home/template/training/pre-post-training-quiz.html" rel="noopener noreferrer"&gt;6 — Pre/post measures&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@formlm/cli" rel="noopener noreferrer"&gt;7 — Agent-assisted prep&lt;/a&gt; · &lt;a href="https://qrcard-ai.com/en/guides/live-message-wall.html" rel="noopener noreferrer"&gt;8 — The debrief&lt;/a&gt; · &lt;a href="https://reportskills.com/en/for/trainers/index.html" rel="noopener noreferrer"&gt;9 — Client reporting&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;*Disclosure: I'm one of the makers — the tools from my own shop named across this series. *&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>productivity</category>
      <category>checklist</category>
    </item>
    <item>
      <title>From One-Off Workshop to Retainer: The Monthly Report Clients Actually Read</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Fri, 06 Nov 2026 01:43:47 +0000</pubDate>
      <link>https://dev.to/keofung/from-one-off-workshop-to-retainer-the-monthly-report-clients-actually-read-2658</link>
      <guid>https://dev.to/keofung/from-one-off-workshop-to-retainer-the-monthly-report-clients-actually-read-2658</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (9/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run workshops for organizations, you've met this client. The workshop went well, everyone said so — and six weeks later the engagement quietly ends with a one-line email: "we're pausing for this quarter."&lt;/p&gt;

&lt;p&gt;Nothing failed. There's just no recurring evidence that anything is happening. A one-off workshop produces one afternoon of proof; a program that reports monthly produces a paper trail of progress, and paper trails get renewed. The uncomfortable truth for those of us who'd rather be in the room than in a spreadsheet: &lt;strong&gt;the monthly report is part of the workshop.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the report needs to be
&lt;/h2&gt;

&lt;p&gt;The failure mode I see is over-reporting: a twelve-tab workbook with pivot tables and a two-page methodology note. Clients don't read it, you resent building it, and it still takes four hours.&lt;/p&gt;

&lt;p&gt;The trainers' version that works is one readable page, and the best formulation of it I've seen is in ReportSkills' own FAQ for the &lt;a href="https://reportskills.com/en/for/trainers/index.html" rel="noopener noreferrer"&gt;consultants and trainers section&lt;/a&gt;: attendance and homework with the session average at the top, a small chart by topic, what the group actually worked on this period, one note about this specific student. Beyond that, resist — a progress report that needs a legend has quietly become a transcript. (For trainers running ongoing classes, the &lt;a href="https://reportskills.com/en/templates/class-progress-report-template/index.html" rel="noopener noreferrer"&gt;class progress report template&lt;/a&gt; implements exactly that shape.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline: paste a table, get a branded PDF
&lt;/h2&gt;

&lt;p&gt;The tool I built for this job is &lt;a href="https://reportskills.com/en/index.html" rel="noopener noreferrer"&gt;ReportSkills&lt;/a&gt;, and its core mechanic is deliberately boring: &lt;strong&gt;paste the table of data you already have, and the report renders live from it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2pkxlwsjn8zw19v4bms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2pkxlwsjn8zw19v4bms.png" alt="ReportSkills: client reports, delivered like clockwork — paste a table, watch the branded PDF render, schedule it to clients monthly." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What that looks like in a workshop-program month: your session notes, the pre/post deltas from part 6 and the feedback scores from part 8 arrive as small tables. Paste each into the template; the report renders immediately — you watch it happen, which is the fastest way to build trust in the tool; then send it per-recipient in a batch, on a schedule, with tracked delivery so you know it actually landed rather than assuming. There's a page of &lt;a href="https://reportskills.com/en/sample-report.html" rel="noopener noreferrer"&gt;sample reports&lt;/a&gt; rendered from fictional table data — marketing, owner statements, bookkeeping and class progress — if you want to see the output before pasting anything:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22pq8m41htz0syniiuzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22pq8m41htz0syniiuzv.png" alt="ReportSkills' sample reports: four rendered outputs from pasted-table data, switchable by kind — the class progress sample is the trainers' one." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The honest mechanics, since I'm the maker and get to set expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templates are widget-composed&lt;/strong&gt; — blocks you arrange per template, not a fixed PDF you can't change. The &lt;a href="https://reportskills.com/en/features.html" rel="noopener noreferrer"&gt;features page&lt;/a&gt; has the full capability list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedules and per-recipient batches&lt;/strong&gt; mean the same report goes out on the same date monthly without you touching it again. That's the "delivered like clockwork" behavior the home page promises, and it's the single feature that most reduces retainer churn, because it makes your presence felt in months you were busy elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No connectors required.&lt;/strong&gt; It's built for the paste-and-CSV route — &lt;a href="https://reportskills.com/en/guides/client-reporting-without-api-access/index.html" rel="noopener noreferrer"&gt;client reporting without API access&lt;/a&gt; is a guide on the site precisely because most training data has no connector: it lives in your notes and your grade sheet. If your data &lt;em&gt;does&lt;/em&gt; live in marketing dashboards, there's a &lt;a href="https://reportskills.com/en/looker-studio-templates/index.html" rel="noopener noreferrer"&gt;Looker Studio template shelf&lt;/a&gt; for that world, and a comparison of &lt;a href="https://reportskills.com/en/compare/reportskills-vs-looker-studio.html" rel="noopener noreferrer"&gt;ReportSkills vs Looker Studio&lt;/a&gt; that's fair about where each wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing is flat&lt;/strong&gt; — Basic free, Plus $19/mo, with a &lt;a href="https://reportskills.com/en/pricing.html" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; that lays out the whole matrix. I mention flat pricing because per-client reporting pricing is the specific trap for trainers: if the tool's price scales with your client count, the tool eats the retainer you just won.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The guide shelf worth reading even if you never buy
&lt;/h2&gt;

&lt;p&gt;ReportSkills ships field guides that stand on their own — genuinely useful, no login, and applicable even if your "pipeline" remains a Google Doc:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reportskills.com/en/guides/monthly-client-report/index.html" rel="noopener noreferrer"&gt;How to write a monthly client report&lt;/a&gt; — the four-block structure and the missing-data rule (the rule alone saves more report emails than any tool: never delay a report over one missing figure)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reportskills.com/en/guides/how-long-should-a-client-report-take/index.html" rel="noopener noreferrer"&gt;How long should a client report take?&lt;/a&gt; — where the hours hide, manual vs templated&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reportskills.com/en/guides/show-clients-value-on-retainer/index.html" rel="noopener noreferrer"&gt;How to show clients their retainer is working&lt;/a&gt; — five moves against churn from invisible results&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reportskills.com/en/guides/email-vs-portal-client-reporting/index.html" rel="noopener noreferrer"&gt;Email PDF, live link or client portal?&lt;/a&gt; — the delivery-format question, answered honestly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wire it to the workshop series
&lt;/h2&gt;

&lt;p&gt;The through-line of this whole series becomes a system at this point. The &lt;a href="https://reportskills.com/en/guides/monthly-client-report/index.html" rel="noopener noreferrer"&gt;monthly client report guide&lt;/a&gt; plus the parts you already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre/post deltas from FormLM (part 6) → the "what changed" block&lt;/li&gt;
&lt;li&gt;Attendance and check-in counts from the QR cards (part 3) → the top-line numbers&lt;/li&gt;
&lt;li&gt;Debrief notes and feedback scores (part 8) → the "what the group actually worked on" block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every workshop you run now feeds the report that renews the program that books the next workshop. That loop — not any single session — is the business.&lt;/p&gt;

&lt;p&gt;Next and last in the series: the whole stack in one place — a printable checklist from invitation to follow-up, with every tool and URL from all ten parts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reportskills.com/en/index.html" rel="noopener noreferrer"&gt;ReportSkills&lt;/a&gt; — recurring branded client reports from pasted tables: &lt;a href="https://reportskills.com/en/features.html" rel="noopener noreferrer"&gt;features&lt;/a&gt;, &lt;a href="https://reportskills.com/en/pricing.html" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;, &lt;a href="https://reportskills.com/en/templates.html" rel="noopener noreferrer"&gt;templates hub&lt;/a&gt;, &lt;a href="https://reportskills.com/en/templates/class-progress-report-template/index.html" rel="noopener noreferrer"&gt;class progress template&lt;/a&gt;, &lt;a href="https://reportskills.com/en/for/trainers/index.html" rel="noopener noreferrer"&gt;for consultants and trainers&lt;/a&gt;, guides on &lt;a href="https://reportskills.com/en/guides/monthly-client-report/index.html" rel="noopener noreferrer"&gt;monthly client reports&lt;/a&gt;, &lt;a href="https://reportskills.com/en/guides/show-clients-value-on-retainer/index.html" rel="noopener noreferrer"&gt;retainer visibility&lt;/a&gt; and &lt;a href="https://reportskills.com/en/guides/how-long-should-a-client-report-take/index.html" rel="noopener noreferrer"&gt;reporting time&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lookerstudio.google.com" rel="noopener noreferrer"&gt;Looker Studio&lt;/a&gt; — the dashboards-side world, with a fair comparison and a template shelf on ReportSkills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Disclosure: I'm one of the makers — the tools from my own shop named across this series. *&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>freelancing</category>
      <category>productivity</category>
      <category>consulting</category>
    </item>
    <item>
      <title>The Debrief Is Where the Learning Happens. Most Facilitators Cut It</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Thu, 05 Nov 2026 05:38:27 +0000</pubDate>
      <link>https://dev.to/keofung/the-debrief-is-where-the-learning-happens-most-facilitators-cut-it-e5g</link>
      <guid>https://dev.to/keofung/the-debrief-is-where-the-learning-happens-most-facilitators-cut-it-e5g</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (8/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a pattern I've watched in hundreds of observed workshops: the activity runs beautifully, the energy in the room is high, and then the facilitator says "Great! So… what did we all think of that?" Three people talk. Five minutes of unstructured conversation later, the clock says time's up, and the facilitator moves on — relieved, because the activity "went well."&lt;/p&gt;

&lt;p&gt;The activity produced an experience. The debrief is where the experience becomes learning. Skip or rush it, and the best activity in the world is just a fun fifteen minutes with a prop budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "what did we think?" fails
&lt;/h2&gt;

&lt;p&gt;Open-ended, opinion-shaped, aimed at the whole room at once. It invites the confident to perform and gives the reflective nothing to chew on. A debrief needs what any good interview needs: a sequence of questions with a job each.&lt;/p&gt;

&lt;p&gt;The sequencing logic I use is the oldest one in facilitation — What? So what? Now what? — and it's exactly why the &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait folder&lt;/a&gt; from part 2 ships its debrief as a &lt;em&gt;script&lt;/em&gt;: eight questions in order, each with a purpose (which instructions were missing, what did you guess instead, where does this happen on your team). You don't have to invent a question sequence per workshop. You have to &lt;em&gt;not skip it when you're behind&lt;/em&gt; — which is why it lives in the run sheet, with minutes attached like any other block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the room a private channel
&lt;/h2&gt;

&lt;p&gt;The biggest debrief handicap is that speaking in front of twenty people is a performance. Two mechanics that pull honest answers out of people who will never raise a hand:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written before spoken.&lt;/strong&gt; Everyone writes one answer on a sticky or a card &lt;em&gt;before&lt;/em&gt; anyone speaks. It's much harder to quietly conform to the group when your answer already exists on paper. The &lt;a href="https://workshopskill.com/en/activities/exit-ticket/" rel="noopener noreferrer"&gt;Exit Ticket folder&lt;/a&gt; is built on exactly this: one written takeaway per person at the door, not a mic passed around the room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An anonymous wall.&lt;/strong&gt; For the questions people won't say out loud ("what's one thing this workshop is missing?"), QRCard's &lt;a href="https://qrcard-ai.com/en/guides/live-message-wall.html" rel="noopener noreferrer"&gt;live message wall&lt;/a&gt; works the way the name suggests: participants scan and post, and messages appear on the screen as a wall — as a grid or drifting bullets, anonymous by design:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3phs6j5euj3eqlni360n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3phs6j5euj3eqlni360n.png" alt="QRCard AI's live message wall: participants scan once and post; reflections appear on screen as a grid or drifting wall" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The facilitator's move that makes the wall earn its keep: read two or three posts out loud and &lt;em&gt;respond to the critical ones first&lt;/em&gt;. The room learns that the anonymous channel reaches the facilitator and gets taken seriously — which is what makes it honest next time. Contrast with part 4's rule (counts, not names): the wall is the one place where anonymous text is the point, but it's still text, not identities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture the trail — for yourself and for the client
&lt;/h2&gt;

&lt;p&gt;Three capture habits that separate professionals from people who run workshops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Record the debrief, with consent.&lt;/strong&gt; I put a note about it in the invite and say it out loud at the start. &lt;a href="https://otter.ai" rel="noopener noreferrer"&gt;Otter.ai&lt;/a&gt; is the lazy, reliable option for transcription — the transcript is for your own debrief-of-the-debrief ("which questions landed, which produced silence"), not for the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collect structured feedback while the room is still warm.&lt;/strong&gt; A feedback card on the table (part 3's QR card, one action, no login) for the two-minute in-room version, and a structured follow-up survey for the next morning — the &lt;a href="https://formlm.me/home/template/training/workshop-feedback-form.html" rel="noopener noreferrer"&gt;workshop feedback form template&lt;/a&gt; from part 6 is the one I use, because the responses come back scored rather than as paragraphs to decode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow up within 48 hours.&lt;/strong&gt; One email: the commitments people wrote at the close (part 1's beat four), the slides or materials, the one thing you'd change next time. Wait a week and you're just another inbox item; within 48 hours, you're still the room they were in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The debrief of the debrief
&lt;/h2&gt;

&lt;p&gt;The step almost everyone skips: ten minutes after the room empties, before you pack the props. Three questions, written in the margin of the run sheet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which block ran over, and what did I cut? (If the answer is "the debrief," fix the timing, not the debrief.)&lt;/li&gt;
&lt;li&gt;Which debrief question produced silence, and what will I replace it with?&lt;/li&gt;
&lt;li&gt;What did the wall say that nobody said out loud?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That margin note is the difference between a workshop you run once and a workshop you get &lt;em&gt;better&lt;/em&gt; at. It's also, incidentally, the raw material for part 9 — because the notes, the measured deltas and the feedback all feed the thing that turns a one-off workshop into a recurring engagement: reporting.&lt;/p&gt;

&lt;p&gt;Next in the series: the monthly report clients actually read — and how to produce it in fifteen minutes instead of an afternoon.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — folders with scripted debriefs and exit tickets: &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait&lt;/a&gt; (8-question debrief), &lt;a href="https://workshopskill.com/en/activities/exit-ticket/" rel="noopener noreferrer"&gt;Exit Ticket&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/activities/four-styles-debrief/" rel="noopener noreferrer"&gt;Four Styles Debrief&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; — the &lt;a href="https://qrcard-ai.com/en/guides/live-message-wall.html" rel="noopener noreferrer"&gt;live message wall&lt;/a&gt; for anonymous, scan-to-post reflections&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;FormLM&lt;/a&gt; — the &lt;a href="https://formlm.me/home/template/training/workshop-feedback-form.html" rel="noopener noreferrer"&gt;workshop feedback form template&lt;/a&gt; for structured, scored post-session feedback&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://otter.ai" rel="noopener noreferrer"&gt;Otter.ai&lt;/a&gt; — consent-on-the-record transcripts for your own review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Disclosure: I'm one of the makers — the tools from my own shop named across this series. *&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>teaching</category>
      <category>reflection</category>
    </item>
    <item>
      <title>I Let an AI Agent Prep My Workshop. Prep Took 20 Minutes.</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Thu, 05 Nov 2026 00:46:54 +0000</pubDate>
      <link>https://dev.to/keofung/i-let-an-ai-agent-prep-my-workshop-prep-took-20-minutes-59dh</link>
      <guid>https://dev.to/keofung/i-let-an-ai-agent-prep-my-workshop-prep-took-20-minutes-59dh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (7/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workshop prep is the part nobody teaches and everybody dreads: read the run sheet, list the props, check print specs, build the sign-up and feedback forms, write the invite. It's all mechanical work that takes an evening — which is exactly the kind of work an AI agent should be doing while you do the human parts.&lt;/p&gt;

&lt;p&gt;The blocker was always that AI couldn't &lt;em&gt;see&lt;/em&gt; your materials. It could draft you an email, but it couldn't open your activity folder, know the group size, count the props, or tell you that the A3 poster needs a color printer. So I made the materials machine-readable, and this post shows the workflow — because it generalizes to any prep materials you keep in files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the prep a skill file
&lt;/h2&gt;

&lt;p&gt;Every folder on &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; ships with a &lt;code&gt;SKILL.md&lt;/code&gt; file — frontmatter (name, description, group size, duration, props) plus runnable prep commands. The point of the format: an AI agent that can read files can &lt;em&gt;discover&lt;/em&gt; the activity, &lt;em&gt;install&lt;/em&gt; it, and &lt;em&gt;run the prep&lt;/em&gt; without you explaining any of it in the chat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpnnvwx1axdh88ttid5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpnnvwx1axdh88ttid5o.png" alt="WorkshopSkill's skills page: every folder installs as a skill for AI agents — a discover, install, prep three-step workflow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The three-step workflow, in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discover.&lt;/strong&gt; The site publishes a machine-readable index — &lt;a href="https://workshopskill.com/en/skills.json" rel="noopener noreferrer"&gt;skills.json&lt;/a&gt;, one entry per folder with the bundle URL and a checksum. Point an agent at it and ask "I need a 20-minute pairs activity on communication for 16 people" and it can shortlist from actual metadata, not vibes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install.&lt;/strong&gt; Download the folder (each has a &lt;code&gt;files/SKILL.md&lt;/code&gt; at a predictable URL — &lt;a href="https://workshopskill.com/en/activities/blind-portrait/files/SKILL.md" rel="noopener noreferrer"&gt;here's Blind Portrait's&lt;/a&gt;, for example). The agent reads the run sheet, the props list and the prep commands the same way you would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the prep.&lt;/strong&gt; This is where it stops being a gimmick. The agent produces the actual checklist: "print 8 prompt cards poker-spec, A4; print the A3 poster; cut cards; you'll need markers; run the pre measure before doors open." It knows all of this because it's &lt;em&gt;in the folder&lt;/em&gt;, not because the model guessed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://workshopskill.com/en/skills.html" rel="noopener noreferrer"&gt;skills-for-agents page&lt;/a&gt; documents the workflow for humans, and the &lt;a href="https://workshopskill.com/en/skill-guides/" rel="noopener noreferrer"&gt;skill tutorials&lt;/a&gt; walk through it per client and per scenario (28 tutorials at time of writing — Claude, Cursor, and the rest, each with its quirks). There's also a plain guide on &lt;a href="https://workshopskill.com/en/guides/ai-agent-workshop-prep/" rel="noopener noreferrer"&gt;AI agent workshop prep&lt;/a&gt; for the concept without the tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forms, via MCP
&lt;/h2&gt;

&lt;p&gt;The prep checklist has always ended with the annoying tail: "build the registration form, build the pre/post measure, build the feedback form." Those are all form-shaped, and form-building is now an MCP-shaped problem.&lt;/p&gt;

&lt;p&gt;FormLM ships an open-source CLI and MCP server (the npm package &lt;a href="https://www.npmjs.com/package/@formlm/cli" rel="noopener noreferrer"&gt;@formlm/cli&lt;/a&gt;). If you use Claude, Cursor or any MCP-compatible client, the whole install is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx -y @formlm/cli mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command, stdio transport, no API keys to wire up — a client with no token signs in through the chat. Then your agent can build the workshop's forms and assessments as part of the same prep session: describe the pre/post measure once, get the live scored form, the registration page and the feedback form — each with a URL you can drop onto a printed card (part 3) or into the invite email.&lt;/p&gt;

&lt;p&gt;So the whole prep conversation with the agent ends up being one message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Install the blind-portrait folder from skills.json, give me the prep checklist, then build the registration form and the pre/post measure on FormLM for my 16-person session Thursday."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Twenty minutes later you have: props checklist, print specs, a run sheet summary, and three live form URLs. The evening you would have spent on this is now the evening you spend rehearsing the debrief questions — the part that actually decides whether the workshop is any good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits, learned the hard way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The agent doesn't run the room.&lt;/strong&gt; It can tell you to print twelve cards; it can't make the printer cooperate. Prep, yes; facilitation, no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine-readable beats clever prompting.&lt;/strong&gt; The workflow works because SKILL.md and skills.json state facts the model can't hallucinate. When I prep with materials that &lt;em&gt;aren't&lt;/em&gt; structured this way, the agent confidently invents group sizes and prop counts — garbage in, confident garbage out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT-style drafting still has a seat.&lt;/strong&gt; For the invite email and the one-page agenda, &lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;chatgpt.com&lt;/a&gt; or &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt; with your prep checklist pasted in is plenty. The agent-with-tools route earns its setup cost when you do this every month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust, but verify.&lt;/strong&gt; I read the agent's checklist against the human-readable folder page every time. Not because it's often wrong — because the one time it's wrong is the night before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in the series: the debrief — the beat where the learning actually happens, and the one most workshops skip.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — every folder ships a SKILL.md; the &lt;a href="https://workshopskill.com/en/skills.html" rel="noopener noreferrer"&gt;skills workflow&lt;/a&gt;, the &lt;a href="https://workshopskill.com/en/skills.json" rel="noopener noreferrer"&gt;skills.json index&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/skill-guides/" rel="noopener noreferrer"&gt;per-agent tutorials&lt;/a&gt; and the &lt;a href="https://workshopskill.com/en/guides/ai-agent-workshop-prep/" rel="noopener noreferrer"&gt;AI agent workshop prep guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;FormLM&lt;/a&gt; — forms and scored assessments; open-source CLI and MCP server, npm package &lt;a href="https://www.npmjs.com/package/@formlm/cli" rel="noopener noreferrer"&gt;@formlm/cli&lt;/a&gt; (&lt;code&gt;npx -y @formlm/cli mcp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; / &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; — drafting the invite and agenda around the checklist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Disclosure: I'm one of the makers — the tools from my own shop named across this series. *&lt;/p&gt;

</description>
      <category>ai</category>
      <category>workshop</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Prove the Workshop Worked: Pre/Post Measures Without the Spreadsheet Pain</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 04 Nov 2026 09:17:53 +0000</pubDate>
      <link>https://dev.to/keofung/prove-the-workshop-worked-prepost-measures-without-the-spreadsheet-pain-582i</link>
      <guid>https://dev.to/keofung/prove-the-workshop-worked-prepost-measures-without-the-spreadsheet-pain-582i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (6/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Part 1 of this series made you write an evidence sentence: "I'll know it worked because ___." This is the part where we actually collect that evidence — because the industry-standard substitute for it is embarrassing.&lt;/p&gt;

&lt;p&gt;The smile sheet — "How satisfied were you with this workshop, 1–5?" — collects politeness, not learning. It can only go up. Nobody rates a workshop badly on the way out the door when the facilitator is standing right there, and even an honest 4.8 tells you nothing about whether participants can &lt;em&gt;do&lt;/em&gt; the thing you promised. If a client asks "did the training work?" and your answer is a satisfaction percentage, you don't have an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measure that actually moves
&lt;/h2&gt;

&lt;p&gt;The design is old and boring and correct: &lt;strong&gt;the same short scored instrument, run before and after the session.&lt;/strong&gt; Before: it measures where people actually are (and calibrates their confidence). After: it measures the same knowledge and self-ratings again. The difference between the two is your evidence.&lt;/p&gt;

&lt;p&gt;If you want the framework language, this is where the Kirkpatrick levels get cited in every L&amp;amp;D conversation — the WorkshopSkill guide on &lt;a href="https://workshopskill.com/en/guides/measure-training-effectiveness/" rel="noopener noreferrer"&gt;measuring training effectiveness&lt;/a&gt; covers the levels without the consultant-speak, and there's a dedicated guide on &lt;a href="https://workshopskill.com/en/guides/pre-post-measures/" rel="noopener noreferrer"&gt;pre/post measures&lt;/a&gt; for the instrument design. The mechanics in the room are simpler than the literature suggests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep it under ten minutes.&lt;/strong&gt; A pre-measure that takes twenty minutes will be sabotaged by late arrivals and coffee logistics before it collects a single honest answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score it, don't just ask it.&lt;/strong&gt; "Rate your confidence 1–5" is an opinion; ten right-or-wrong items plus two confidence self-ratings is a measure. Scored instruments are the difference between vibes and deltas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same items, same order, both times.&lt;/strong&gt; If the post-test differs from the pre-test, your "improvement" is noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  From paper instrument to automatic before/after deltas
&lt;/h2&gt;

&lt;p&gt;Here's where I stopped doing this the hard way. For years my pre/post measure lived in a Google Form (&lt;a href="https://forms.google.com" rel="noopener noreferrer"&gt;forms.google.com&lt;/a&gt;) — free, fine, and completely manual afterwards: export two spreadsheets, VLOOKUP participants by hand (the ones who spelled their email differently in round two), compute deltas, build a chart per person for the client. That's the "spreadsheet pain" in this post's title, and it's the reason most facilitators quietly skip the pre/post entirely.&lt;/p&gt;

&lt;p&gt;The tool I built to make this automatic is &lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;FormLM&lt;/a&gt; — you describe a diagnostic once, and it generates the questions, the scoring logic, and a print-ready PDF report for every respondent:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbj5drd27a2yzrcffctb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbj5drd27a2yzrcffctb9.png" alt="FormLM: describe your diagnostic once — questions, scoring logic and a branded PDF report per respondent come back automatically" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The template I start from for workshops is the &lt;a href="https://formlm.me/home/template/training/pre-post-training-quiz.html" rel="noopener noreferrer"&gt;pre/post-training quiz&lt;/a&gt;: the same scored quiz runs before and after training, every attempt gets graded automatically, and FormLM computes the before/after deltas into a per-participant report — the knowledge lift in plain numbers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8681sec5l22ej8js5cnd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8681sec5l22ej8js5cnd.png" alt="FormLM's pre/post-training quiz template: run the same scored quiz twice, get before/after deltas and a knowledge-lift report per participant." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What that buys you at the end of a session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-participant before/after deltas&lt;/strong&gt;, computed — not hand-joined from two exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PDF report per respondent&lt;/strong&gt; (scored, branded), which is the artifact you forward to a sponsoring manager who asks "so what did my team actually get?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An honest aggregate:&lt;/strong&gt; "average knowledge lift from 41 to 63 points, biggest gains on the two items about X."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The surrounding templates I keep in rotation for the full workshop measurement loop: the &lt;a href="https://formlm.me/home/template/training/workshop-feedback-form.html" rel="noopener noreferrer"&gt;workshop feedback form&lt;/a&gt; (the &lt;em&gt;useful&lt;/em&gt; kind of post-session survey — structured, not smile sheet), the &lt;a href="https://formlm.me/home/template/training/training-evaluation-form.html" rel="noopener noreferrer"&gt;training evaluation form&lt;/a&gt; for program-level evaluation, and the &lt;a href="https://formlm.me/home/template/training/post-training-survey-template.html" rel="noopener noreferrer"&gt;post-training survey&lt;/a&gt; for the two-weeks-later check on what stuck. There's a whole &lt;a href="https://formlm.me/home/template/training/index.html" rel="noopener noreferrer"&gt;training template category&lt;/a&gt; if you run recurring programs.&lt;/p&gt;

&lt;p&gt;One connection worth knowing if you use both tools from this series: the WorkshopSkill folders each ship with a pre/post measure in the folder, and FormLM is the engine that turns that measure into a live scored form with the deltas computed — the folder is the paper instrument, FormLM is the scoring desk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free tools when you just need the math
&lt;/h2&gt;

&lt;p&gt;Two situations where you don't need a platform at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You already have scores in a spreadsheet&lt;/strong&gt; and just need the charts for a debrief slide: FormLM's free in-browser tools — the &lt;a href="https://formlm.me/home/tools/index.html" rel="noopener noreferrer"&gt;radar chart maker&lt;/a&gt;, the Likert scale and weighted-score calculators — run without an account and upload nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The measure is one question&lt;/strong&gt; ("how confident are you running this activity, 1–10?" on the way in and on the way out): a whiteboard and a show of hands is the honest, zero-tool version. Do this one even when you have the platform — it makes the lift &lt;em&gt;visible to the room in real time&lt;/em&gt;, which is worth more than any PDF.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What measurement won't do for you
&lt;/h2&gt;

&lt;p&gt;Honest limits, because overselling measurement is its own industry disease:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pre/post lift proves learning happened &lt;strong&gt;in the room&lt;/strong&gt;, not that behavior changed back at the desk. For behavior, you need a follow-up at two to four weeks (the post-training survey above) and a client who actually wants to know.&lt;/li&gt;
&lt;li&gt;Don't measure more than you'll report. Ten items you'll show the sponsor beat forty items you'll never open again.&lt;/li&gt;
&lt;li&gt;If the workshop is genuinely just "exposure" — a one-hour talk with no practice — measuring learning is theater. Part 1's outcome sentence decides whether a pre/post is even appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in the series: the part I get the most pushback on — handing the prep work to an AI agent, on purpose.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://formlm.me/home/web.html" rel="noopener noreferrer"&gt;FormLM&lt;/a&gt; — AI form and assessment platform with scored pre/post measures, automatic before/after deltas and a PDF report per respondent: &lt;a href="https://formlm.me/home/template/training/pre-post-training-quiz.html" rel="noopener noreferrer"&gt;pre/post-training quiz template&lt;/a&gt;, &lt;a href="https://formlm.me/home/template/training/workshop-feedback-form.html" rel="noopener noreferrer"&gt;workshop feedback form&lt;/a&gt;, &lt;a href="https://formlm.me/home/template/training/training-evaluation-form.html" rel="noopener noreferrer"&gt;training evaluation form&lt;/a&gt;, &lt;a href="https://formlm.me/home/template/training/index.html" rel="noopener noreferrer"&gt;training templates&lt;/a&gt;, &lt;a href="https://formlm.me/home/tools/index.html" rel="noopener noreferrer"&gt;free in-browser tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — guides on &lt;a href="https://workshopskill.com/en/guides/measure-training-effectiveness/" rel="noopener noreferrer"&gt;measuring training effectiveness&lt;/a&gt; and &lt;a href="https://workshopskill.com/en/guides/pre-post-measures/" rel="noopener noreferrer"&gt;pre/post measures&lt;/a&gt;; every folder ships a pre/post measure&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://forms.google.com" rel="noopener noreferrer"&gt;Google Forms&lt;/a&gt; — free, fine, and fully manual afterwards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. *&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>teaching</category>
      <category>productivity</category>
      <category>assessment</category>
    </item>
    <item>
      <title>Group Work That Doesn't Collapse Into Silence</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 04 Nov 2026 02:06:12 +0000</pubDate>
      <link>https://dev.to/keofung/group-work-that-doesnt-collapse-into-silence-3ojo</link>
      <guid>https://dev.to/keofung/group-work-that-doesnt-collapse-into-silence-3ojo</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (5/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You've seen the failure mode: "Okay, get into groups of four and discuss!" — followed by thirty seconds of chair-scraping negotiation, one loud table, one table of people checking phones, and a debrief where the same three people speak.&lt;/p&gt;

&lt;p&gt;Group work fails for boring, mechanical reasons. Not because humans are lazy — because the grouping was social roulette, the task had no artifact, and the clock was invisible. All three are fixable before the room ever fills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: Group people like you deal cards, not like you pick teams
&lt;/h2&gt;

&lt;p&gt;Self-selected groups are the most expensive thing you can do to a room of strangers: the confident cluster, the friends pair off, and whoever is left over absorbs the message that they were chosen last. Randomization isn't a gimmick — it's the fastest way to make "who's in my group" a non-event so the group can get to work.&lt;/p&gt;

&lt;p&gt;What I use: QRCard's &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;random group generator&lt;/a&gt;. It runs in the browser, free, nothing gets uploaded, and it does more than count-off-to-four:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg41dpka7w03njxtj0gge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg41dpka7w03njxtj0gge.png" alt="QRCard AI's random group generator: pairs, group sizes, rotations and cross-matching — the room gets dealt fairly instead of negotiating." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pairs and arbitrary group sizes&lt;/strong&gt;, obviously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotations&lt;/strong&gt;, for when you want the same pairs to meet twice — round one, then a deal that keeps each pair together across rounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-matching&lt;/strong&gt;, for when you want everyone to have met everyone — each round pairs people who haven't worked together yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third mode is the sleeper feature. For a workshop whose real purpose is "these twenty people should know each other afterwards," cross-matching turns your grouping mechanic into the outcome itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Never give a group a topic — give them an artifact
&lt;/h2&gt;

&lt;p&gt;"Discuss the challenges of stakeholder management" produces silence. "Draw your project's stakeholders on this sheet, mark who blocks whom, be ready to present in 12 minutes" produces arguments, laughter, and a thing that exists afterwards.&lt;/p&gt;

&lt;p&gt;The difference is that a topic invites opinions, but an artifact demands &lt;em&gt;decisions&lt;/em&gt;. Every time you feel a group drifting, the missing ingredient is something physical to produce. This is why the activity folders from part 2 all ship with props: &lt;a href="https://workshopskill.com/en/activities/minefield/" rel="noopener noreferrer"&gt;Minefield&lt;/a&gt; has its printed grid cards, &lt;a href="https://workshopskill.com/en/activities/silent-lineup/" rel="noopener noreferrer"&gt;Silent Lineup&lt;/a&gt; has the constraint, &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait&lt;/a&gt; has poker-spec prompt cards, and nearly every folder has an A3 poster designed for exactly this — the group's shared canvas, sized for a table or a wall.&lt;/p&gt;

&lt;p&gt;The template for any task you invent yourself: &lt;strong&gt;noun + verb + visible output + deadline.&lt;/strong&gt; "List" is weak; "rank," "draw," "map," "build a top-three" are strong. If you can't name the thing the group will be holding up at the end, the task isn't ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: Make the clock public and ruthless
&lt;/h2&gt;

&lt;p&gt;Groups don't stall at minute two; they stall at minute seven, when the easy surface is exhausted and nobody remembers how much time is left. Two mechanics that fix this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visible countdown.&lt;/strong&gt; The run-of-show timer in the WorkshopSkill presenter deck exists precisely for this. When "three minutes left" appears on the projector without you saying a word, the group self-manages — you don't have to play the enforcer walking between tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midpoint callout.&lt;/strong&gt; At half-time, say one sentence: "You should have your first draft of the map by now." Groups that lost the thread get a reset; groups that finished early start improving instead of chatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Remote and hybrid rooms
&lt;/h2&gt;

&lt;p&gt;The same three fixes apply, with one substitution each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grouping:&lt;/strong&gt; the same random generator works — paste the attendee list, deal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifact:&lt;/strong&gt; this is where &lt;a href="https://miro.com" rel="noopener noreferrer"&gt;Miro&lt;/a&gt; and &lt;a href="https://www.figma.com/figjam/" rel="noopener noreferrer"&gt;FigJam&lt;/a&gt; genuinely earn their place. A shared board &lt;em&gt;is&lt;/em&gt; the A3 poster for remote groups. My honest usage note: pre-build the board template (frames, sticky zones, a titled area per group) before the session — dropping people into a blank infinite canvas produces the same silence as "discuss." The WorkshopSkill guide on &lt;a href="https://workshopskill.com/en/guides/remote-hybrid-workshops/" rel="noopener noreferrer"&gt;remote and hybrid workshops&lt;/a&gt; has a fuller checklist for this; for long-form remote programs, running it over &lt;a href="https://zoom.us" rel="noopener noreferrer"&gt;Zoom&lt;/a&gt; breakout rooms plus a pre-built board is the boring, reliable stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clock:&lt;/strong&gt; put the countdown in the board itself or name a timer in chat. Remote groups can't see the room's clock; give them one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The safety floor
&lt;/h2&gt;

&lt;p&gt;One caveat that outranks all the mechanics: none of this works if speaking up feels dangerous. If groups go quiet &lt;em&gt;after&lt;/em&gt; you've fixed grouping, artifacts and clocks, the problem is safety, not structure — &lt;a href="https://workshopskill.com/en/guides/psychological-safety/" rel="noopener noreferrer"&gt;psychological safety&lt;/a&gt; is the guide I point people at for that layer. The short version: model fallibility yourself early ("here's where I got this wrong last month"), protect the first person who says something half-baked, and never let a group's output be mocked in the debrief — critique the artifact, never the makers.&lt;/p&gt;

&lt;p&gt;Next in the series: the part most facilitators skip and most clients quietly wish they'd asked for — proving the workshop actually worked.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; — the &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;random group generator&lt;/a&gt; (pairs, rotations, cross-matching, free and in-browser)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — activity folders with props, A3 posters and presenter-deck timers: &lt;a href="https://workshopskill.com/en/activities/minefield/" rel="noopener noreferrer"&gt;Minefield&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/activities/silent-lineup/" rel="noopener noreferrer"&gt;Silent Lineup&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait&lt;/a&gt;; guides on &lt;a href="https://workshopskill.com/en/guides/remote-hybrid-workshops/" rel="noopener noreferrer"&gt;remote and hybrid workshops&lt;/a&gt; and &lt;a href="https://workshopskill.com/en/guides/psychological-safety/" rel="noopener noreferrer"&gt;psychological safety&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://miro.com" rel="noopener noreferrer"&gt;Miro&lt;/a&gt; and &lt;a href="https://www.figma.com/figjam/" rel="noopener noreferrer"&gt;FigJam&lt;/a&gt; — the remote room's shared artifact; &lt;a href="https://zoom.us" rel="noopener noreferrer"&gt;Zoom&lt;/a&gt; — breakout rooms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. Every claim about them matches what the products do today, and links go to the live pages. This series was drafted with AI assistance and reviewed before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>productivity</category>
      <category>teaching</category>
    </item>
    <item>
      <title>Open the Room Without Forcing an App on Everyone</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 04 Nov 2026 00:52:11 +0000</pubDate>
      <link>https://dev.to/keofung/open-the-room-without-forcing-an-app-on-everyone-380i</link>
      <guid>https://dev.to/keofung/open-the-room-without-forcing-an-app-on-everyone-380i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (4/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every facilitator knows the moment: twenty minutes into your workshop you want the room's input. "Grab your phones and go to..." — and then you watch the energy drain out through people's thumbs while they type a URL, miss the slash, install something, or stare at a join code on your slide.&lt;/p&gt;

&lt;p&gt;The tools we reach for by habit — &lt;a href="https://www.mentimeter.com" rel="noopener noreferrer"&gt;Mentimeter&lt;/a&gt;, &lt;a href="https://www.slido.com" rel="noopener noreferrer"&gt;Slido&lt;/a&gt;, &lt;a href="https://ahaslides.com" rel="noopener noreferrer"&gt;AhaSlides&lt;/a&gt; — are good at what they do. They run live quizzes, word clouds, ranked voting. But the default joining flow is a toll booth: open browser or app, type a join code, maybe log in, &lt;em&gt;then&lt;/em&gt; you can participate. In a two-hour workshop with strangers, a chunk of the room never gets through the booth at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The joining cost is the whole ballgame
&lt;/h2&gt;

&lt;p&gt;For a workshop (as opposed to a conference keynote), I optimize for one variable: &lt;strong&gt;seconds from deciding to participating.&lt;/strong&gt; Everything else — word clouds, fancy chart types — is decoration if the join costs ninety seconds.&lt;/p&gt;

&lt;p&gt;The setup I've converged on uses the printed card from part 3, but pointed at the big screen instead of at a clipboard. &lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; has a &lt;a href="https://qrcard-ai.com/en/guides/trainer-live-screen.html" rel="noopener noreferrer"&gt;trainer live screen&lt;/a&gt;: you project the card onto the room's display, people scan once with their phone — no app, no login, no join code — and their check-ins, sign-ups and answers appear on the screen in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcaxo5yrdbhhnzi32qhu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcaxo5yrdbhhnzi32qhu6.png" alt="The QRCard AI trainer live screen: project one card, the room scans once, and responses land on the big screen in real time." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What this buys you on stage, concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A live read on the room's state.&lt;/strong&gt; Seats filling as people arrive; answers landing as a card stream while you talk. The screen becomes the room's shared awareness instead of your private spreadsheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presenter control from the clicker.&lt;/strong&gt; Pause it, fullscreen it, freeze it when time's up — you don't need to walk back to the laptop mid-sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It survives bad venue Wi-Fi.&lt;/strong&gt; Any facilitator who has watched a cloud-based poll die on conference-hall Wi-Fi mid-activity knows this is not a nice-to-have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private by default.&lt;/strong&gt; It projects counts, not names and emails. The shy person in row three can answer honestly because the room sees "17 people picked B," not her name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're comparing options, QRCard publishes its own honest comparison pages — &lt;a href="https://qrcard-ai.com/en/compare/mentimeter-alternative.html" rel="noopener noreferrer"&gt;vs Mentimeter&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/compare/slido-alternative.html" rel="noopener noreferrer"&gt;vs Slido&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/compare/ahaslides-alternative.html" rel="noopener noreferrer"&gt;vs AhaSlides&lt;/a&gt; — which state plainly where each tool wins. Those tools genuinely do more poll types; that's why they charge per seat. My line is: &lt;strong&gt;when participants are strangers and the session is short, joining cost beats feature count.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The first ten minutes: pairing instead of polling
&lt;/h2&gt;

&lt;p&gt;Before any screen, the cheapest participation tech is still another human. The fastest way to wake up a room of strangers is to put them in pairs doing something slightly ridiculous, and the fastest way to do &lt;em&gt;that&lt;/em&gt; fairly is to remove the social calculus of "who do I sit with?"&lt;/p&gt;

&lt;p&gt;Two moves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Randomize the pairing.&lt;/strong&gt; Letting people pick means cliques pair up and the anxious person at the edge of the room gets picked last — or not at all. QRCard's &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;random group generator&lt;/a&gt; does pairs, rotations and cross-matching in the browser, free, nothing uploaded. You paste your list or just set the group size; it deals the room.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the pair something with rules.&lt;/strong&gt; This is where the activity folders from part 2 earn their keep: &lt;a href="https://workshopskill.com/en/activities/two-truths/" rel="noopener noreferrer"&gt;Two Truths and a Lie&lt;/a&gt; (two minutes, zero props, both people talk), or the &lt;a href="https://workshopskill.com/en/guides/icebreaker-games/" rel="noopener noreferrer"&gt;icebreaker games guide&lt;/a&gt; when you need options for a specific kind of room.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sequencing that works for me: pairs first (voices already warm), &lt;em&gt;then&lt;/em&gt; the big screen (the room is now comfortable being visible). Reverse the order and the first thing the room learns about your workshop is that being anonymous is the safe state.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I don't use the screen for
&lt;/h2&gt;

&lt;p&gt;The live screen is a scalpel, not the workshop. Things I deliberately keep off it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anything long.&lt;/strong&gt; If the task takes more than sixty seconds of typing, the screen version will strand people mid-sentence when you advance. Long reflection goes on paper (part 8's exit tickets).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything that names people.&lt;/strong&gt; The moment the screen shows names, participation becomes a performance. Counts only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything you don't plan to &lt;em&gt;use&lt;/em&gt;.&lt;/strong&gt; If you collect answers and never reference them again, the room learns that answering is theater. One screen moment, genuinely used, beats five decorative ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in the series: the hardest fifteen minutes of any workshop — group work that doesn't collapse into silence, and how to actually run it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; — &lt;a href="https://qrcard-ai.com/en/guides/trainer-live-screen.html" rel="noopener noreferrer"&gt;trainer live screen&lt;/a&gt; for projecting a card to the room display, &lt;a href="https://qrcard-ai.com/en/guides/audience-participation-without-app.html" rel="noopener noreferrer"&gt;audience participation without an app&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/guides/random-group-generator.html" rel="noopener noreferrer"&gt;random group generator&lt;/a&gt;, honest comparisons: &lt;a href="https://qrcard-ai.com/en/compare/mentimeter-alternative.html" rel="noopener noreferrer"&gt;vs Mentimeter&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/compare/slido-alternative.html" rel="noopener noreferrer"&gt;vs Slido&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/compare/ahaslides-alternative.html" rel="noopener noreferrer"&gt;vs AhaSlides&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mentimeter.com" rel="noopener noreferrer"&gt;Mentimeter&lt;/a&gt;, &lt;a href="https://www.slido.com" rel="noopener noreferrer"&gt;Slido&lt;/a&gt;, &lt;a href="https://ahaslides.com" rel="noopener noreferrer"&gt;AhaSlides&lt;/a&gt; — the established poll/quiz tools; more poll types, more joining friction, per-seat pricing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — &lt;a href="https://workshopskill.com/en/activities/two-truths/" rel="noopener noreferrer"&gt;Two Truths and a Lie folder&lt;/a&gt; and the &lt;a href="https://workshopskill.com/en/guides/icebreaker-games/" rel="noopener noreferrer"&gt;icebreaker games guide&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. Every claim about them matches what the products do today, and links go to the live pages. This series was drafted with AI assistance and reviewed before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>productivity</category>
      <category>events</category>
    </item>
    <item>
      <title>Registration and Check-in That Runs Itself: Put the Paper to Work</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 04 Nov 2026 00:50:41 +0000</pubDate>
      <link>https://dev.to/keofung/registration-and-check-in-that-runs-itself-put-the-paper-to-work-h69</link>
      <guid>https://dev.to/keofung/registration-and-check-in-that-runs-itself-put-the-paper-to-work-h69</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (3/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Watch what happens at the average workshop check-in table: a printed list, a pen, someone crossing out names, and a line of people spelling their email addresses at each other. Meanwhile the facilitator is trying to remember who said they were vegetarian and whether the waitlist people got told the venue changed.&lt;/p&gt;

&lt;p&gt;The root cause is that most of us treat registration and check-in as &lt;em&gt;paperwork&lt;/em&gt; instead of as the first five minutes of the workshop experience. It's the first thing participants touch. It sets the tone for how organized the rest of the day will feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three anti-patterns
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The long URL.&lt;/strong&gt; You paste a Google Form link into an email, and at the venue you tape a printout of a QR code pointing at &lt;code&gt;docs.google.com/forms/d/1Xk9...&lt;/code&gt; with no explanation of what happens after scanning. People won't scan what they don't trust, and they shouldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The static QR.&lt;/strong&gt; You print a code that points at one fixed page. Then the venue changes, the capacity drops from 30 to 20, the deadline moves — and every printed code is now wrong. Reprinting everything is the only fix, so nobody does it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The app wall.&lt;/strong&gt; "Just download this event app and enter code WORKSHOP-2026-AB." For a two-hour session, the download is more expensive than the ticket.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What paper actually wants to be
&lt;/h2&gt;

&lt;p&gt;The fix I've landed on — and yes, this is my own tool, I'll be honest about that up front — is to treat the printed artifact as the interface: a &lt;strong&gt;QR Card&lt;/strong&gt;. &lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; turns one sentence ("Sign up for the October design workshop, 20 seats") into a printable card that collects data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqe0sejg115xydbu5jz6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqe0sejg115xydbu5jz6g.png" alt="QRCard AI: printable cards that collect sign-ups, check-ins and feedback — the card face tells people why to scan" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three properties make this work where the anti-patterns fail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The card face explains itself.&lt;/strong&gt; A QR code floating alone on a page is sketchy; a card that says what scanning does — "Sign up: 20 seats, closes Friday" — earns the scan. There's a good short explainer on &lt;a href="https://qrcard-ai.com/en/guides/how-cards-work.html" rel="noopener noreferrer"&gt;how QR Cards work&lt;/a&gt; and what makes a card trustworthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One scan, one action.&lt;/strong&gt; Registration, check-in, feedback, whatever the card is for — after the scan, the person does one thing and they're done. No account, no login, no join code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live status flows back to the printed card.&lt;/strong&gt; This is the part that sold me on the model: spots left, deadlines, waitlists update dynamically — the printed code keeps pointing at a page that always knows the truth. The card is printed once; everything behind it can change anytime. That's the &lt;a href="https://qrcard-ai.com/en/guides/dynamic-qr-code.html" rel="noopener noreferrer"&gt;dynamic QR&lt;/a&gt; behavior, and it kills the "static QR" anti-pattern dead.&lt;/p&gt;

&lt;p&gt;To see the range of what people run on these, the &lt;a href="https://qrcard-ai.com/en/examples.html" rel="noopener noreferrer"&gt;examples gallery&lt;/a&gt; shows twelve complete sample cards — club sign-ups, event pre-registration, class sign-up slips, feedback cards, each scannable right now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnft9o7q0qzmkcdqzusz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnft9o7q0qzmkcdqzusz.png" alt="The QRCard AI examples gallery: twelve fictional but fully scannable sample cards — sign-ups, check-ins, feedback, waitlists." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a workshop specifically, I run two cards: a &lt;strong&gt;sign-up card&lt;/strong&gt; (goes out in the invite email and gets printed for the noticeboard) and a &lt;strong&gt;check-in card&lt;/strong&gt; (on the door the morning of). Both free-tier friendly — the &lt;a href="https://qrcard-ai.com/en/pricing.html" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; has a free plan with up to 10 live cards, which is more than a workshop season needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The room's paper layer
&lt;/h2&gt;

&lt;p&gt;Two more pieces of workshop paper that people never regret:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Wi-Fi card.&lt;/strong&gt; Instead of people hunting for the venue network name and shouting passwords across tables, a &lt;a href="https://qrcard-ai.com/en/guides/wifi-qr-code.html" rel="noopener noreferrer"&gt;Wi-Fi QR code&lt;/a&gt; on each table connects phones in one scan. It's a free in-browser generator — nothing gets uploaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A feedback card&lt;/strong&gt;, which I'll cover properly in part 8 (the debrief and follow-up), because it belongs to the end of the arc, not the beginning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When you need something heavier
&lt;/h2&gt;

&lt;p&gt;Being fair about the boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paid tickets, refunds, seat maps?&lt;/strong&gt; That's a ticketing system. &lt;a href="https://www.eventbrite.com" rel="noopener noreferrer"&gt;Eventbrite&lt;/a&gt; does the money-handling and receipting properly; a QR card doesn't and shouldn't. The two compose fine — Eventbrite takes the payment, a card handles day-of check-in and feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling one-on-ones before or after the session?&lt;/strong&gt; &lt;a href="https://calendly.com" rel="noopener noreferrer"&gt;Calendly&lt;/a&gt; or &lt;a href="https://cal.com" rel="noopener noreferrer"&gt;Cal.com&lt;/a&gt; still owns that job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A long, scored assessment&lt;/strong&gt; (pre/post measures, the subject of part 6) shouldn't be crammed onto a card either — the card collects; the assessment platform scores. There's an honest comparison of exactly this split on QRCard's own site: &lt;a href="https://qrcard-ai.com/en/compare/formlm-vs-qr-card.html" rel="noopener noreferrer"&gt;FormLM vs QR Card&lt;/a&gt;, which says plainly that a long instrument belongs to a form platform, not to printed paper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb I use: &lt;strong&gt;cards for one action, platforms for processes.&lt;/strong&gt; Registration is one action. Check-in is one action. That's why they keep running themselves while the facilitator is doing the actual job — being in the room.&lt;/p&gt;

&lt;p&gt;Next in the series: opening the room — live participation without forcing an app on everyone.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://qrcard-ai.com/en/index.html" rel="noopener noreferrer"&gt;QRCard AI&lt;/a&gt; — printable QR Cards for workshop sign-ups, check-ins and feedback: &lt;a href="https://qrcard-ai.com/en/guides/how-cards-work.html" rel="noopener noreferrer"&gt;how cards work&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/examples.html" rel="noopener noreferrer"&gt;examples gallery&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/guides/dynamic-qr-code.html" rel="noopener noreferrer"&gt;dynamic QR behavior&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/guides/wifi-qr-code.html" rel="noopener noreferrer"&gt;free in-browser Wi-Fi QR generator&lt;/a&gt;, &lt;a href="https://qrcard-ai.com/en/pricing.html" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.eventbrite.com" rel="noopener noreferrer"&gt;Eventbrite&lt;/a&gt; — paid ticketing, refunds, receipts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://calendly.com" rel="noopener noreferrer"&gt;Calendly&lt;/a&gt; / &lt;a href="https://cal.com" rel="noopener noreferrer"&gt;Cal.com&lt;/a&gt; — scheduling the conversations around the session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. Every claim about them matches what the products do today, and links go to the live pages. This series was drafted with AI assistance and reviewed before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>productivity</category>
      <category>events</category>
    </item>
    <item>
      <title>Steal a Run Sheet: What a Ready-to-Run Workshop Folder Contains</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Tue, 03 Nov 2026 09:31:10 +0000</pubDate>
      <link>https://dev.to/keofung/steal-a-run-sheet-what-a-ready-to-run-workshop-folder-contains-570j</link>
      <guid>https://dev.to/keofung/steal-a-run-sheet-what-a-ready-to-run-workshop-folder-contains-570j</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (2/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In part 1 I argued you should design the workshop before the slides, and I teased a thing called a run sheet. Here's the confession that goes with it: for years I built every one of those from scratch. Run sheet in a doc, deck in Google Slides, props in whatever drawing tool was closest, instructions on loose paper, the debrief question list in my head.&lt;/p&gt;

&lt;p&gt;Every one of those artifacts died a separate, un-versioned death after the session. The next time I ran a similar workshop, I started over.&lt;/p&gt;

&lt;p&gt;Then I flipped the unit of preparation from "a bunch of files" to &lt;strong&gt;one folder per activity&lt;/strong&gt;, and I built &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; around that idea. This post walks through what a finished folder actually contains, because the anatomy matters more than any single piece — and because you can build your own version of this filing system with whatever tools you like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anatomy of an activity folder
&lt;/h2&gt;

&lt;p&gt;Take a real one. &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait&lt;/a&gt; — a classic back-to-back drawing exercise, 2 people per pair, 15 minutes, offline, one person describes an image while the other draws it without seeing it. Simple activity. Here's everything that ships in the folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcf7hmvnaffm85gjrrhc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcf7hmvnaffm85gjrrhc0.png" alt="The Blind Portrait folder page on WorkshopSkill: group size, duration, setting, props, license and reference — everything about the activity on one card" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A minute-by-minute run sheet.&lt;/strong&gt; Not "introduce the activity" but exactly what you say to brief it, when pairs swap roles, when pens down. The run sheet is the artifact that survives contact with a real room: when the fire alarm kills ten minutes, you cut blocks, you don't improvise the debrief away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A presenter-grade HTML deck.&lt;/strong&gt; It opens in a browser and it's built for stage use: a presenter drawer with your notes, a run-of-show timer visible only to you, and a built-in QR engine — you can put a join code on screen in one step. I'll come back to that QR engine in part 4, because it's the bridge to live participation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Print-and-cut props.&lt;/strong&gt; The drawing prompts are poker-card spec — print, cut along the lines, done. No "find a suitable image online and hope it prints at the right size."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An A3 wall poster.&lt;/strong&gt; The artifact the room builds on together, sized for the wall, not for a laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A debrief script.&lt;/strong&gt; The actual questions, in order, with the sequencing logic written down. Not "discuss what happened" — "which instructions were the describer missing? what did you guess instead?" Part 8 of this series covers why this file is the most important one in the folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pre/post measure.&lt;/strong&gt; A short scored instrument that runs before and after the session, so "it felt great" becomes a number that moved. Part 6 covers what to do with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The manual, in A4 and US Letter.&lt;/strong&gt; Both sizes, with PDF bookmarks, because printers and continents vary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A white-label guide&lt;/strong&gt;, so if you're a consultant delivering under a client's brand, the materials don't shout someone else's name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the whole folder is downloadable as a zip, free, under CC BY — attribution and you're done. There are paid tiers if you need a single-event or organization license, but the free default is genuinely usable: no account, no paywall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "a deck" is the wrong unit
&lt;/h2&gt;

&lt;p&gt;Compare that to the default workflow — building in Google Slides (&lt;a href="https://slides.google.com" rel="noopener noreferrer"&gt;slides.google.com&lt;/a&gt;) or Canva (&lt;a href="https://canva.com" rel="noopener noreferrer"&gt;canva.com&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slides have no clock.&lt;/strong&gt; A deck doesn't know your room started eight minutes late. A run-of-show timer does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slides can't hold the run sheet, the props and the debrief script&lt;/strong&gt; without becoming a 60-page monster where the presenter has to hunt for the cue cards mid-session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decks die alone.&lt;/strong&gt; The deck is on one laptop, the props never got printed, the debrief questions lived in your head and left with you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is an attack on slide tools — I still use slides for content-heavy teaching blocks, and Canva makes a gorgeous one-pager. The point is the &lt;em&gt;unit of preparation&lt;/em&gt;: if the folder is the unit, everything travels together, gets versioned together, and is ready to run again next quarter without archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to steal from, and what to file yourself
&lt;/h2&gt;

&lt;p&gt;The blunt advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stop writing activities from scratch.&lt;/strong&gt; A well-run Two Truths, Minefield or Silent Lineup has been refined in thousands of rooms before yours. &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; exists precisely so you can pull a folder and go — the &lt;a href="https://workshopskill.com/en/activities/" rel="noopener noreferrer"&gt;folder categories&lt;/a&gt; are organized by what the activity trains (communication, trust, conflict, leadership, icebreakers and more), and the &lt;a href="https://workshopskill.com/en/guides/" rel="noopener noreferrer"&gt;how-to guide library&lt;/a&gt; covers the surrounding craft: &lt;a href="https://workshopskill.com/en/guides/design-a-workshop/" rel="noopener noreferrer"&gt;designing a workshop&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/guides/icebreaker-games/" rel="noopener noreferrer"&gt;icebreaker games&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/guides/large-group-activities/" rel="noopener noreferrer"&gt;large-group activities&lt;/a&gt;, &lt;a href="https://workshopskill.com/en/guides/psychological-safety/" rel="noopener noreferrer"&gt;psychological safety&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File your own activities the same way.&lt;/strong&gt; After every session you run, spend ten minutes upgrading your materials into the folder shape: run sheet, props spec, debrief script, measure. The discipline is the value; the site is just one implementation of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License properly when you borrow.&lt;/strong&gt; CC BY means "credit the author, run it anywhere." The &lt;a href="https://workshopskill.com/en/licensing.html" rel="noopener noreferrer"&gt;licensing page&lt;/a&gt; spells out the free tier and the paid tiers for commercial contexts that need them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;Folders don't fix everything. A run sheet can't rescue a workshop aimed at no outcome — that's part 1's job. Props don't make an activity fit a room it wasn't designed for — check the folder details (group size, duration, setting) before you commit, not after the room fills up. And printing is still yours to do the day before; no archive prints your props for you.&lt;/p&gt;

&lt;p&gt;What the folder unit buys you is the thing veteran facilitators all have and few can name: &lt;strong&gt;a shelf you can pull from at 9 p.m. the night before, and trust.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next in the series: registration and check-in — the part of workshops everyone pretends is solved by pasting a long URL into an email.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — free archive of ready-to-run activity folders; see the &lt;a href="https://workshopskill.com/en/activities/blind-portrait/" rel="noopener noreferrer"&gt;Blind Portrait folder&lt;/a&gt;, the &lt;a href="https://workshopskill.com/en/activities/" rel="noopener noreferrer"&gt;full activity categories&lt;/a&gt;, the &lt;a href="https://workshopskill.com/en/guides/" rel="noopener noreferrer"&gt;guides library&lt;/a&gt; and &lt;a href="https://workshopskill.com/en/licensing.html" rel="noopener noreferrer"&gt;licensing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://slides.google.com" rel="noopener noreferrer"&gt;Google Slides&lt;/a&gt; — fine for teaching blocks; wrong unit for the whole workshop&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.canva.com" rel="noopener noreferrer"&gt;Canva&lt;/a&gt; — one-pagers and visual materials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. Every claim about them matches what the products do today, and links go to the live pages. This series was drafted with AI assistance and reviewed before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>teaching</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Design the Workshop Before You Build the Slides</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Tue, 03 Nov 2026 09:29:31 +0000</pubDate>
      <link>https://dev.to/keofung/design-the-workshop-before-you-build-the-slides-5dab</link>
      <guid>https://dev.to/keofung/design-the-workshop-before-you-build-the-slides-5dab</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: How to Run a Workshop People Remember (1/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've sat through a lot of bad workshops. When I ask the facilitator afterwards what went wrong, the answer is almost never "my slides were ugly." It's always some version of the same confession: &lt;em&gt;"I built the deck first and figured out the workshop along the way."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The deck is not the workshop. The deck is packaging. If you design the session in Keynote or Google Slides, you'll get a presentation with a bit of discussion bolted on — which is exactly the format people have learned to sleep through.&lt;/p&gt;

&lt;p&gt;This series is about the whole job of running a workshop that people actually remember: design, materials, registration, the room itself, measurement, follow-up, and the boring-but-lucrative part — reporting to whoever paid for it. This first part is the foundation: how to design before you build anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with two sentences
&lt;/h2&gt;

&lt;p&gt;Before you open any tool, write these two sentences on paper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The outcome:&lt;/strong&gt; "By the end of this session, participants can ___." One verb, one observable behavior. Not "understand stakeholder mapping" — "can draw a stakeholder map for their own project and name their two riskiest relationships."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The evidence:&lt;/strong&gt; "I'll know it worked because ___." What will you see or measure that proves the outcome happened? A map on the wall per team? A pre/post score? A commitment written down before people leave?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you can't finish sentence two, your workshop is a lecture with snacks. Everything else in this series — activities, QR screens, measurement, reporting — exists to make the evidence sentence cheap to collect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four-beat arc
&lt;/h2&gt;

&lt;p&gt;Every workshop I've run or admired follows the same rhythm. Steal it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beat 1 — Open (10–15%).&lt;/strong&gt; Low-risk warmup that gets voices into the room. It's not entertainment; it's establishing that speaking here is safe. A ninety-second pair exercise like Two Truths and a Lie does more than thirty minutes of icebreaker bingo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beat 2 — Explore (50%).&lt;/strong&gt; The actual work, done by participants, not by you. This is where tools and props earn their keep — timed exercises, group artifacts, visible outputs. You talk the least in this beat. That's the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beat 3 — Converge (20%).&lt;/strong&gt; The debrief. This is where learning actually happens, and it's the beat most facilitators cut when they run late. (Part 8 of this series is entirely about this, because it deserves its own article.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beat 4 — Close (10%).&lt;/strong&gt; Each person leaves with one commitment written in their own words. If they leave with nothing written, the workshop evaporates on the drive home.&lt;/p&gt;

&lt;h2&gt;
  
  
  From arc to run-of-show
&lt;/h2&gt;

&lt;p&gt;Now assign minutes. A run-of-show is the workshop cut into timed blocks with an owner per block: not "team activity," but "Minefield, 20 min, pairs, needs 8 printed cards, debrief 5 min." When something runs over — something always runs over — the run-of-show tells you exactly what to cut without gutting the debrief.&lt;/p&gt;

&lt;p&gt;I stopped writing slide outlines years ago and now design every session as a run sheet first. If you want to see what a finished one looks like, the folders on &lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; each ship with a minute-by-minute run sheet — that's the artifact I'm describing, filed and ready to compare against your own. There's also a dedicated guide there on &lt;a href="https://workshopskill.com/en/guides/design-a-workshop/" rel="noopener noreferrer"&gt;how to design a workshop&lt;/a&gt; that goes deeper on outcomes and timing than this post can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI and tools fit in design (and where they don't)
&lt;/h2&gt;

&lt;p&gt;I use AI at exactly two points in the design phase, and both are drafting, not deciding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharpening the outcome sentence.&lt;/strong&gt; Paste your draft outcome into ChatGPT (&lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;chatgpt.com&lt;/a&gt;) or Claude (&lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt;) with a prompt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's my draft learning outcome for a 3-hour workshop: [outcome]. Rewrite it as one observable behavior per participant, list three ways I could see or measure that behavior during the session, and flag anything vague."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a superb vagueness detector. It will catch every "understand" and "appreciate" hiding in your sentence. It will not know your participants; you still own the call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pressure-testing the arc.&lt;/strong&gt; Give it your four beats with minutes and ask: "Which beat is underweighted for a group of 25 who don't know each other?" The answers are generic but often embarrassing in a useful way — usually it points out that your Open beat is two minutes shorter than it should be.&lt;/p&gt;

&lt;p&gt;For the plan itself, I keep it boring: one page in &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; or any doc with a table — beat, minutes, activity, materials, evidence captured. If the design doesn't fit one page, it won't fit the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's coming in this series
&lt;/h2&gt;

&lt;p&gt;The next nine parts walk the whole pipeline with the tools I actually use — mine and other people's, named and linked, so you can assemble your own stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stealing a run sheet: what a ready-to-run activity folder contains&lt;/li&gt;
&lt;li&gt;Registration and check-in that runs itself&lt;/li&gt;
&lt;li&gt;Opening the room without forcing an app on everyone&lt;/li&gt;
&lt;li&gt;Group work that doesn't collapse into silence&lt;/li&gt;
&lt;li&gt;Proving it worked: pre/post measures without the spreadsheet pain&lt;/li&gt;
&lt;li&gt;Letting an AI agent do the prep work&lt;/li&gt;
&lt;li&gt;The debrief: where learning actually happens&lt;/li&gt;
&lt;li&gt;From one-off workshop to monthly retainer (reporting clients read)&lt;/li&gt;
&lt;li&gt;The whole stack: a checklist from invitation to follow-up&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Tools in this post:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://workshopskill.com/en/" rel="noopener noreferrer"&gt;WorkshopSkill&lt;/a&gt; — free archive of ready-to-run workshop activity folders, each with a minute-by-minute run sheet; see the &lt;a href="https://workshopskill.com/en/guides/design-a-workshop/" rel="noopener noreferrer"&gt;design-a-workshop guide&lt;/a&gt; and &lt;a href="https://workshopskill.com/en/theory.html" rel="noopener noreferrer"&gt;the theory behind filing activities like skills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; and &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; — outcome sharpening and arc pressure-tests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; — the one-page plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm one of the makers of WorkshopSkill, QRCard AI, FormLM and ReportSkills — the tools from my own shop named across this series. Every claim about them matches what the products do today, and links go to the live pages. This series was drafted with AI assistance and reviewed before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workshop</category>
      <category>facilitation</category>
      <category>teaching</category>
      <category>productivity</category>
    </item>
    <item>
      <title>50 Forms in 30 Days: What I Learned Building Assessment Forms With an AI Agent</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 12 Aug 2026 03:43:50 +0000</pubDate>
      <link>https://dev.to/keofung/50-forms-in-30-days-what-i-learned-building-assessment-forms-with-an-ai-agent-10io</link>
      <guid>https://dev.to/keofung/50-forms-in-30-days-what-i-learned-building-assessment-forms-with-an-ai-agent-10io</guid>
      <description>&lt;p&gt;This is the last post in the series. Over the past 30 days, I used Claude Desktop and the formlm-cli MCP server to build 50 assessment forms — everything from a 3-question mood check to a 40-item leadership 360 evaluation. Some were for production, some were tests, some were experiments to see how far I could push the AI.&lt;/p&gt;

&lt;p&gt;Here's what worked, what didn't, and what I'd do differently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kq5lb04sv4ci9ol2cnj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kq5lb04sv4ci9ol2cnj.png" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;Over 30 days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forms created:&lt;/strong&gt; 50 (12 production, 38 testing/experimentation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total fields added:&lt;/strong&gt; ~640 (average ~13 fields per form)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP tool calls:&lt;/strong&gt; ~3,200 (average ~64 per form)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-induced bugs:&lt;/strong&gt; 7 (details below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time per form:&lt;/strong&gt; 30 seconds for simple forms, 3-5 minutes for complex ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual fixes needed:&lt;/strong&gt; 23 forms needed at least one manual correction after AI build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The manual fix rate (46%) is the number I want to drive down. It means roughly half the forms the AI built weren't ready without human intervention. The bugs weren't catastrophic — but they were real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 Bugs
&lt;/h2&gt;

&lt;p&gt;Here's every AI-induced bug I hit, ranked by severity:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The &lt;code&gt;field_update&lt;/code&gt; Options Wipe (Critical)
&lt;/h3&gt;

&lt;p&gt;Claude replaced an entire options list with a single option, destroying the other options' scores. This is the find-then-set pattern story from article 12. Fixed by adding &lt;code&gt;field_set_property&lt;/code&gt; and enforcing the find-first pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Staging/Production Profile Leak (High)
&lt;/h3&gt;

&lt;p&gt;Claude published a form to staging when I asked for production. This is the multi-profile story from article 18. Fixed by making the environment visible in every response — still in progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The &lt;code&gt;assess form clear&lt;/code&gt; Attempt (High)
&lt;/h3&gt;

&lt;p&gt;Claude tried to bulk-delete all fields in a form. Blocked by the server-side whitelist. This is the whitelist story from article 19. No fix needed — the whitelist caught it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Duplicate Fields (Medium)
&lt;/h3&gt;

&lt;p&gt;Claude re-added all fields after a session restart, creating duplicates. This is the idempotency story from article 15. Fixed by enforcing find-before-add.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The Boolean Trap (Medium)
&lt;/h3&gt;

&lt;p&gt;Claude couldn't set &lt;code&gt;required: false&lt;/code&gt; — it kept omitting the parameter, which defaulted to &lt;code&gt;true&lt;/code&gt;. This is the boolean trap story from article 14. Fixed by making the schema description explicit about the default.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The Reverse Scoring Confusion (Low)
&lt;/h3&gt;

&lt;p&gt;Claude inverted option scores when it should have used scale-level reversal. This is the scoring story from article 17. Fixed by being explicit about what kind of "reverse" I mean in prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. The Forgotten App ID (Low)
&lt;/h3&gt;

&lt;p&gt;Claude lost track of the app ID mid-session and tried to add fields without it. This is from the stress test in article 13. Fix idea: maintain a "current app" state in the MCP server — not implemented yet.&lt;/p&gt;

&lt;p&gt;Four of the seven bugs were caught before reaching production. Three made it to staging. None made it to production users. The whitelist was the hardest backstop — everything else was caught by manual review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Worked Well
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Schema-Driven Discovery
&lt;/h3&gt;

&lt;p&gt;Claude consistently called &lt;code&gt;field_schema&lt;/code&gt; and &lt;code&gt;field_config&lt;/code&gt; before adding fields. It treated the &lt;code&gt;.describe()&lt;/code&gt; strings like documentation — reading them, understanding them, and making decisions based on them. This is the single most important design decision I made: making every parameter self-documenting through zod schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Natural Language to Field Structure
&lt;/h3&gt;

&lt;p&gt;Claude was excellent at translating assessment frameworks (PHQ-9, Maslach Burnout, Big Five) into field structures. It knew the items, the scoring ranges, and the dimensional groupings. Give it a framework name and it would produce the right fields with the right options and scores. This saved hours of manual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe Defaults
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;share_publish&lt;/code&gt; tool hardcodes &lt;code&gt;formDay: 3650000&lt;/code&gt; (never expires) and &lt;code&gt;formPerm: 1&lt;/code&gt; (public read). The AI never had to decide on expiry or permissions — it just published. Safe defaults eliminated an entire class of potential mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Whitelist
&lt;/h3&gt;

&lt;p&gt;Every single time Claude tried something it shouldn't have (the &lt;code&gt;clear&lt;/code&gt; attempt, the &lt;code&gt;delete-all&lt;/code&gt; variant), the whitelist caught it. Not one dangerous command slipped through. The three-token subcommand extraction was simple, fast, and effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Didn't Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  State Management Across Sessions
&lt;/h3&gt;

&lt;p&gt;Claude doesn't remember what it did in a previous session. When I asked it to "continue building," it started from scratch — re-creating fields that already existed. The find-before-add pattern helped, but the fundamental issue is that MCP tools are stateless. There's no "current app" or "last field added" concept.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex Scoring Configuration
&lt;/h3&gt;

&lt;p&gt;The MCP tools handle form construction well. They don't handle scoring at all. For any form that needed dimensional scoring, reverse items, or score cutoffs, I had to configure the Scale module manually. This was the biggest gap — about 70% of the forms I built needed manual scoring configuration after the AI finished the form structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Boolean Parameters
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;unique&lt;/code&gt;, and &lt;code&gt;shareable&lt;/code&gt; boolean parameters were consistently problematic. Claude's instinct to omit rather than explicitly set &lt;code&gt;false&lt;/code&gt; caused silent bugs in multiple forms. The schema description fix helped, but it's a band-aid on a deeper design issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Awareness
&lt;/h3&gt;

&lt;p&gt;Claude had no concept of "staging" vs "production." It used whatever profile was active and didn't know the difference. The URL in the response was the only signal, and Claude didn't flag it when the environment didn't match my request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns That Emerged
&lt;/h2&gt;

&lt;p&gt;Over 30 days, I noticed consistent patterns in how Claude interacted with the tools:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude always checks before adding.&lt;/strong&gt; Before the first &lt;code&gt;field_add&lt;/code&gt;, Claude calls &lt;code&gt;field_schema&lt;/code&gt; and &lt;code&gt;field_config&lt;/code&gt;. Every single time. This is good — it means the schema descriptions are doing their job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude batches similar operations.&lt;/strong&gt; When adding 10 fields, Claude doesn't ask for confirmation between each one. It adds all 10 in sequence. This is fast, but it means if the first field is wrong, the next 9 are probably wrong too (same pattern, same mistake).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude verifies after building.&lt;/strong&gt; After adding fields, Claude calls &lt;code&gt;field_list&lt;/code&gt; to check. This is a self-verification step that catches missing fields but doesn't catch scoring errors (since &lt;code&gt;field_list&lt;/code&gt; shows structure, not scoring logic).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude gets creative when blocked.&lt;/strong&gt; When Claude couldn't find a bulk-delete tool, it tried shell commands. When it couldn't set &lt;code&gt;required: false&lt;/code&gt;, it tried omitting the parameter. The AI doesn't give up — it finds workarounds. Some of those workarounds are clever. Some are dangerous. The whitelist is what keeps the dangerous ones in check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I were starting over, I'd make these changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a &lt;code&gt;field_add_many&lt;/code&gt; batch tool.&lt;/strong&gt; Reduce 10 round-trips to 1. But define clear error semantics — if one field in the batch fails, return the error for that field and let the AI decide whether to retry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make the "current app" implicit.&lt;/strong&gt; After &lt;code&gt;app_create&lt;/code&gt; or &lt;code&gt;app_get&lt;/code&gt;, set the app ID as the active context. Subsequent field operations default to the active app. This eliminates the "forgotten app ID" bug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expose scoring configuration via MCP.&lt;/strong&gt; This is the biggest gap. Without it, the AI can only build the form shell — not the assessment engine. I'm working on this, but it requires careful whitelist design (AI can set scoring, but not reset it).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add environment labels to every response.&lt;/strong&gt; "Published on staging.formlm.me" not just the URL. Make the environment impossible to miss.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make boolean parameters required, not optional.&lt;/strong&gt; If &lt;code&gt;required&lt;/code&gt; must be &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, never &lt;code&gt;undefined&lt;/code&gt;, Claude can't fall into the "omit means default" trap. The extra parameter in every call is worth the safety.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Meta-Lesson
&lt;/h2&gt;

&lt;p&gt;The biggest thing I learned over 30 days is this: &lt;strong&gt;designing tools for AI agents is different from designing tools for humans.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I design a CLI for humans, I optimize for ergonomics — sensible defaults, helpful flags, clear error messages. Humans read the docs, understand the context, and make informed decisions.&lt;/p&gt;

&lt;p&gt;When I design an MCP server for AI agents, I optimize for explicitness — no hidden defaults, no ambiguous parameters, no operations that require context the AI doesn't have. Every tool needs to be self-contained: the description tells the AI what it does, the schema tells the AI what to pass, and the response tells the AI what happened.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.describe()&lt;/code&gt; string on every zod field isn't just documentation. It's the instruction manual the AI reads before deciding to use the tool. A vague description ("Mark as required") leads to vague behavior. An explicit description ("Defaults to true if omitted. Set to false explicitly for optional fields.") leads to correct behavior.&lt;/p&gt;

&lt;p&gt;This series has been about the journey from "the code works" to "the AI can use the code correctly." The first part — writing working code — took a few weeks. The second part — making it AI-safe — has taken 30 days and counting, and I'm still finding edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The formlm-cli MCP server now has 6 layered tools + 6 knowledge resources, a server-side whitelist, explicit schema descriptions, and the patterns (find-then-set, find-before-add) that make AI interactions safe. The CLI is open source. The platform is live at formlm.me.&lt;/p&gt;

&lt;p&gt;We're not there yet. But 50 forms in 30 days has proven that the foundation works. The tools are solid. The patterns are reliable. The whitelist holds. The AI can build forms.&lt;/p&gt;

&lt;p&gt;Now it can build assessments too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This concludes the 20-part series on building AI-native tooling for FormLM. The CLI and MCP server are open source at &lt;a href="https://github.com/formlm/cli" rel="noopener noreferrer"&gt;github.com/formlm/cli&lt;/a&gt;. The platform is at &lt;a href="https://formlm.me" rel="noopener noreferrer"&gt;formlm.me&lt;/a&gt;. Thanks for reading.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The Whitelist That Saved My Production Data: A Security Boundary Story</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Wed, 12 Aug 2026 03:42:09 +0000</pubDate>
      <link>https://dev.to/keofung/the-whitelist-that-saved-my-production-data-a-security-boundary-story-1nl7</link>
      <guid>https://dev.to/keofung/the-whitelist-that-saved-my-production-data-a-security-boundary-story-1nl7</guid>
      <description>&lt;p&gt;I almost lost every field in a production form last week. Not to a bug, not to a crash — to an AI that was trying to be helpful. If I hadn't built a server-side command whitelist, I'd be writing a very different post right now.&lt;/p&gt;

&lt;p&gt;Here's what happened, how the whitelist caught it, and why I'll never ship an AI-accessible API without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I was cleaning up a staging form that had accumulated junk fields from various test runs. I told Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This form has too many test fields. Remove all the fields that start with 'test' and clean it up."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude looked at the field list, saw about 15 fields starting with "test_", and decided the most efficient approach was not to call &lt;code&gt;field_remove&lt;/code&gt; 15 times. Instead, it tried something... creative.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Tried
&lt;/h2&gt;

&lt;p&gt;Claude called &lt;code&gt;field_remove&lt;/code&gt; for individual test fields — that part was fine. But after the fifth call, it apparently decided individual removals were too slow. It tried to find a "remove all" or "clear" command.&lt;/p&gt;

&lt;p&gt;The MCP tools don't expose a "clear all fields" operation. But Claude was looking at the CLI command structure, and it noticed that the underlying server commands follow a pattern: &lt;code&gt;assess form &amp;lt;subcommand&amp;gt;&lt;/code&gt;. If &lt;code&gt;assess form remove&lt;/code&gt; removes one field, maybe &lt;code&gt;assess form clear&lt;/code&gt; removes all of them?&lt;/p&gt;

&lt;p&gt;Claude didn't have a &lt;code&gt;field_clear&lt;/code&gt; MCP tool. But it could construct the raw CLI command and... wait, it couldn't. The MCP server only exposes tools, not raw command execution. Every MCP tool call goes through &lt;code&gt;execCommand(cmd)&lt;/code&gt;, where &lt;code&gt;cmd&lt;/code&gt; is a string built from the tool parameters.&lt;/p&gt;

&lt;p&gt;But here's the thing — the CLI itself is installed on my machine. Claude Desktop could, in theory, run shell commands. And it did. Claude opened a terminal and tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;formlm-cli field clear &lt;span class="nt"&gt;--app&lt;/span&gt; abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI doesn't have a &lt;code&gt;field clear&lt;/code&gt; command. It failed with "unknown command." But Claude wasn't done. It tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;formlm-cli assess form clear &lt;span class="nt"&gt;--app&lt;/span&gt; abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one sent a request to the server. The server received &lt;code&gt;assess form clear --app abc123&lt;/code&gt;. And that's where the whitelist kicked in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Whitelist
&lt;/h2&gt;

&lt;p&gt;On the server side, every command sent through &lt;code&gt;/api/v1/mcp/exec&lt;/code&gt; goes through a whitelist check. The whitelist operates at the subcommand level — not the module level, not the individual field level, but the specific subcommand.&lt;/p&gt;

&lt;p&gt;The server extracts the first three space-separated tokens from the command string and checks them against an allowlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assess app list      ✅ allowed
assess app create    ✅ allowed
assess app update    ✅ allowed
assess app remove    ✅ allowed
assess form add      ✅ allowed
assess form update   ✅ allowed
assess form remove   ✅ allowed
assess form move     ✅ allowed
assess form find     ✅ allowed
assess form query    ✅ allowed
assess form types    ✅ allowed
assess form config   ✅ allowed
assess form set-property  ✅ allowed
assess share set     ✅ allowed
assess share query   ✅ allowed
assess share url     ✅ allowed
...
assess form clear    ❌ BLOCKED
assess app clear     ❌ BLOCKED
assess form delete-all  ❌ BLOCKED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;assess form clear&lt;/code&gt; was not on the allowlist. The server returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Command not allowed: assess form clear"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude got the error. It tried once more with a slight variation (&lt;code&gt;assess form clear-all&lt;/code&gt;), got the same 403, and then gave up and went back to removing fields one by one. The whitelist held.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Subcommand-Level, Not Module-Level
&lt;/h2&gt;

&lt;p&gt;I could have built the whitelist at the module level — allow everything under &lt;code&gt;assess form *&lt;/code&gt; and block everything else. But that would have been too permissive. The &lt;code&gt;assess form&lt;/code&gt; module has both safe operations (add, update, find, query) and dangerous ones (clear, delete-all, reset). Module-level whitelisting would either allow everything (dangerous) or block everything (useless).&lt;/p&gt;

&lt;p&gt;Subcommand-level whitelisting is more precise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;assess form add&lt;/code&gt; — safe, creates data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assess form remove&lt;/code&gt; — safe in isolation, removes one field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assess form clear&lt;/code&gt; — DANGEROUS, removes ALL fields&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assess form move&lt;/code&gt; — safe, reorders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assess form update&lt;/code&gt; — safe, modifies one field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dangerous operations are the "bulk" ones — &lt;code&gt;clear&lt;/code&gt;, &lt;code&gt;delete-all&lt;/code&gt;, &lt;code&gt;reset&lt;/code&gt;. These are administrative commands that a human would run intentionally but an AI might trigger accidentally (or "creatively," as in my case).&lt;/p&gt;

&lt;h2&gt;
  
  
  What Would Have Happened Without the Whitelist
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;assess form clear&lt;/code&gt; had been allowed, the server would have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deleted every field in the form — not just the "test_" fields, ALL of them&lt;/li&gt;
&lt;li&gt;Returned a success response&lt;/li&gt;
&lt;li&gt;Left me with a published form that had zero fields&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Respondents clicking the form URL would see an empty page. No error, no warning — just an empty form. The data wouldn't be recoverable because &lt;code&gt;clear&lt;/code&gt; doesn't soft-delete; it hard-deletes.&lt;/p&gt;

&lt;p&gt;I had about 200 responses on that form from real users. The responses would have been orphaned — response data without corresponding field definitions. The reporting module would have crashed trying to map response data to non-existent fields.&lt;/p&gt;

&lt;p&gt;It would have been a very bad day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extract Function
&lt;/h2&gt;

&lt;p&gt;The key piece of the whitelist is the subcommand extraction. It takes the raw command string and extracts the first three tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_sub_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it checks against the allowlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess app list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess app create&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess app update&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess app remove&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form add&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form update&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form remove&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form find&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form types&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form config&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess form set-property&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess share set&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess share query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assess share url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_sub_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. Three tokens. If the combination isn't in the set, it's rejected. No regex, no pattern matching, no partial matching. Just a set lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works for AI Agents
&lt;/h2&gt;

&lt;p&gt;The whitelist works because it's &lt;strong&gt;server-side&lt;/strong&gt;. The AI can't bypass it. Even if Claude somehow figures out a way to send raw HTTP requests (which it shouldn't be able to through the MCP server), the server still checks the command against the whitelist.&lt;/p&gt;

&lt;p&gt;The whitelist works because it's &lt;strong&gt;explicit&lt;/strong&gt;. There's no "allow everything except..." logic. It's a pure allowlist — if it's not on the list, it's not allowed. This means I can't accidentally forget to block a dangerous command; I can only forget to &lt;em&gt;allow&lt;/em&gt; a safe one (which is a much safer failure mode).&lt;/p&gt;

&lt;p&gt;The whitelist works because it's &lt;strong&gt;at the right granularity&lt;/strong&gt;. Blocking at the module level is too coarse. Blocking at the parameter level (e.g., "don't allow &lt;code&gt;--app&lt;/code&gt; with certain IDs") is too fine. The subcommand level — &lt;code&gt;assess form clear&lt;/code&gt; vs &lt;code&gt;assess form remove&lt;/code&gt; — is the sweet spot. It's where the semantic difference between "delete one" and "delete all" lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;After this incident, I made three changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Added more blocked commands to the documentation.&lt;/strong&gt; The MCP tool descriptions now explicitly say "There is no bulk delete operation available." Claude doesn't need to go looking for one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Added logging.&lt;/strong&gt; Every blocked command is now logged with the timestamp, the command string, and the user token (to identify which AI agent tried it). I can see if Claude (or any other MCP client) attempts a blocked command.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Added a "dangerous operations" review.&lt;/strong&gt; Once a month, I review the full command list and ask: "Is there any subcommand that an AI might reasonably try that I haven't explicitly allowed or blocked?" It's a manual process, but it catches edge cases.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whitelist is the most boring piece of security infrastructure in the whole system. It's a set lookup. It's not fancy. But it's the one thing that stood between my production data and an AI that was trying to be efficient.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The formlm-cli MCP server is backed by a server-side command whitelist that blocks bulk operations. Open source at &lt;a href="https://github.com/formlm/cli" rel="noopener noreferrer"&gt;github.com/formlm/cli&lt;/a&gt; — try the platform at &lt;a href="https://formlm.me" rel="noopener noreferrer"&gt;formlm.me&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
