<?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>[2/10]Designing a YAML-Based Task Planning System for AI-Driven Application Generation</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:38:24 +0000</pubDate>
      <link>https://dev.to/keofung/210designing-a-yaml-based-task-planning-system-for-ai-driven-application-generation-1ddd</link>
      <guid>https://dev.to/keofung/210designing-a-yaml-based-task-planning-system-for-ai-driven-application-generation-1ddd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: Building a Modular Assessment Engine (2/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The plan agent's job sounds simple: read a user's request, decide which modules to run, output a task list. In practice, this was the hardest component to get right. One wrong decision in planning cascades through every downstream module.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strategy Decision Matrix
&lt;/h2&gt;

&lt;p&gt;The core of the plan agent is a &lt;strong&gt;decision matrix&lt;/strong&gt; — a table that maps user intent to a module sequence. I tried free-form AI planning first. It was a disaster. The AI would invent modules that didn't exist, skip required ones, or order them wrong.&lt;/p&gt;

&lt;p&gt;The matrix constrains the AI to six plan types, each with a fixed module sequence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;planType&lt;/th&gt;
&lt;th&gt;Module Sequence&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;consultation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→scale→connect→report→expert→share&lt;/td&gt;
&lt;td&gt;Assessment + AI expert follow-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assessment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→scale→connect→report→share&lt;/td&gt;
&lt;td&gt;Self-assessment with PDF report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→scale→connect→report→share&lt;/td&gt;
&lt;td&gt;External evaluation/rating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→scale→connect→report→share&lt;/td&gt;
&lt;td&gt;Knowledge test with correct answers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;survey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→connect→share&lt;/td&gt;
&lt;td&gt;Data collection, no scoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;learn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;form→connect→share&lt;/td&gt;
&lt;td&gt;Flashcard learning with quick quizzes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule is strict: &lt;strong&gt;the AI cannot generate modules outside this table.&lt;/strong&gt; If the user asks for something that doesn't fit any row, the AI picks the closest match. No free-form module creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The YAML Output Contract
&lt;/h2&gt;

&lt;p&gt;The plan agent outputs pure YAML, wrapped in code fences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;planType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assessment&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Workplace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Stress&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Assessment'&lt;/span&gt;
&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;form&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Design&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Assessment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Form'&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3-dimension&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stress&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;13&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;questions'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Scene: assessment. Name field: "Your Name".&lt;/span&gt;
      &lt;span class="s"&gt;3 dimensions:&lt;/span&gt;
      &lt;span class="s"&gt;- Workload (5 questions, measures perceived work intensity)&lt;/span&gt;
      &lt;span class="s"&gt;- Role Stress (4 questions, measures role ambiguity and conflict)&lt;/span&gt;
      &lt;span class="s"&gt;- Social Support (4 questions, measures workplace support networks)&lt;/span&gt;
      &lt;span class="s"&gt;Total: 13 questions. Use scale type (1-5 Likert).&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Configure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Scoring'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;3 dimensions, all negative direction (high score = worse).&lt;/span&gt;
      &lt;span class="s"&gt;Workload: 5 questions, sum format.&lt;/span&gt;
      &lt;span class="s"&gt;Role Stress: 4 questions, sum format.&lt;/span&gt;
      &lt;span class="s"&gt;Social Support: 4 questions, sum format.&lt;/span&gt;
      &lt;span class="s"&gt;3-tier ranges per dimension (severe/moderate/mild).&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect&lt;/span&gt;
    &lt;span class="na"&gt;lookAndFeel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;theme:Noir;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;look:workplace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stress,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;psychological&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assessment,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dark&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;professional;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;layout:stack'&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Configure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pages&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Style'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Cover: hook with 73% statistic, 5-minute estimate, dimension preview.&lt;/span&gt;
      &lt;span class="s"&gt;Main: field mode (one question per page).&lt;/span&gt;
      &lt;span class="s"&gt;Final: report-pdf with download enabled.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report&lt;/span&gt;
    &lt;span class="na"&gt;lookAndFeel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;theme:Noir;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;look:dark&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;professional,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;consistent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;connect'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;3 dimensions, theme:Noir. Radar chart for summary.&lt;/span&gt;
      &lt;span class="s"&gt;Per-dimension: high/mid/low 3-tier interpretation.&lt;/span&gt;
      &lt;span class="s"&gt;Total score: low=good, high=needs support (symptom scale).&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;share&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Publish'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Anonymous access, one submission per person, permanent validity.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Knowledge Self-Containment Rule
&lt;/h3&gt;

&lt;p&gt;This is the most important rule in the entire system, and it took three iterations to enforce:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each task's &lt;code&gt;knowledge&lt;/code&gt; field is the ONLY context that skill receives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Task 1 (form) knows nothing about Task 2 (scale). Task 2 knows nothing about Task 3 (connect). When the scale skill runs, it doesn't see what the form skill generated — it only sees its own &lt;code&gt;knowledge&lt;/code&gt; field plus runtime references (the actual form fields that now exist in the database).&lt;/p&gt;

&lt;p&gt;Why? Because the execution engine runs each task as an independent AI call. There's no conversation history between tasks. This was a deliberate design choice for reliability — if Task 3 fails, Task 4 can still run because it doesn't depend on Task 3's chat history.&lt;/p&gt;

&lt;p&gt;But it means the plan agent must be explicit. If the form task says "3 dimensions: Workload, Role Stress, Social Support," the scale task must repeat those exact names. The connect task must mention them for the cover page preview. The report task must list them for interpretation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Strong Consistency Rule
&lt;/h3&gt;

&lt;p&gt;The plan agent enforces &lt;strong&gt;verbatim name consistency&lt;/strong&gt; across tasks. If the form task calls a dimension "Workload," the scale task cannot call it "Workload Intensity." The names must match character-for-character because downstream modules do string comparison.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In the execution engine, scale dimension names are matched against&lt;/span&gt;
&lt;span class="c1"&gt;// form field groupings by exact string comparison&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Scale&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getScales&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;scaleName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// This must EXACTLY match the dimension name in the form task's knowledge&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;formKnowledge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scaleName&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;FLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Scale name mismatch: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;scaleName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Went Wrong: The "Same As Above" Problem
&lt;/h2&gt;

&lt;p&gt;Version 1 of the plan agent didn't enforce self-containment. The AI, being efficient, would write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Task 1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;3 dimensions: Workload, Role Stress, Social Support.&lt;/span&gt;
    &lt;span class="s"&gt;13 questions total.&lt;/span&gt;

&lt;span class="c1"&gt;# Task 2 — the AI got lazy&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale&lt;/span&gt;
  &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;Same 3 dimensions as above. Sum format, 3-tier ranges.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Same 3 dimensions as above" — except there is no "above" when the scale skill runs. It's a fresh AI call with only this knowledge block. The scale skill would generate 3 empty dimensions named "Dimension 1," "Dimension 2," "Dimension 3" because it had no idea what the actual dimension names were.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Went Wrong: Phantom Modules
&lt;/h2&gt;

&lt;p&gt;Version 2 introduced a different problem. The AI would sometimes add a &lt;code&gt;feedback&lt;/code&gt; or &lt;code&gt;analytics&lt;/code&gt; module that doesn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;form&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analytics&lt;/span&gt;   &lt;span class="c1"&gt;# ← doesn't exist&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;share&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution engine would crash trying to load &lt;code&gt;skills/assess/skill-analytics/SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix: the available skills are now &lt;strong&gt;dynamically injected&lt;/strong&gt; into the plan agent's system prompt at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;skillsSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;skillsSection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"## Available Skills\n\n"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;skillsSection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Each task's `skill` field MUST be selected from this list:\n\n"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;skillId&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;BUILDER_SKILL_IDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;AssessPlanSkill&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getOrCacheSkill&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"skills/assess/skill-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;skillId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/SKILL.md"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;skillsSection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"- `"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skillId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"`: "&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSummary&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentContent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;skillsSection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI sees the exact list of available skills in its system prompt. No more phantom modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Went Wrong: PageTemplates Leakage
&lt;/h2&gt;

&lt;p&gt;The plan agent used to output a &lt;code&gt;pageTemplates&lt;/code&gt; field in report tasks — a list of template IDs for the report module. This was fragile because the AI would sometimes output invalid template names, or forget to include them entirely.&lt;/p&gt;

&lt;p&gt;The current solution: the plan agent &lt;strong&gt;never outputs pageTemplates&lt;/strong&gt;. Instead, it declares two values in the report task's knowledge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimN=3
theme=Noir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution engine reads these values and computes the page template list automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In AssessAgent.skillGenerateStream()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skillName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nc"&gt;ObjectUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPageTemplates&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Recover from scale data — count actual dimensions in memory&lt;/span&gt;
    &lt;span class="nc"&gt;Factor&lt;/span&gt; &lt;span class="n"&gt;_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFlower&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getModel&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getFactor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_factor&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;ObjectUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_factor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getScales&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_dimCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getScales&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_recovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buildPageTemplates&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_dimCount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;planType&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPageTemplates&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_recovered&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;FLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pageTemplates recovered from scale data, dimCount="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_dimCount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;runtime fallback&lt;/strong&gt;: even if the plan agent messes up, the execution engine can reconstruct the page template list from the actual scale dimensions in memory. The AI doesn't need to know about template IDs at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The LookAndFeel Field
&lt;/h2&gt;

&lt;p&gt;Three tasks can carry a &lt;code&gt;lookAndFeel&lt;/code&gt; field: connect, report, and expert. This is a semicolon-delimited string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;theme:Noir; look:workplace stress assessment, dark professional, deep blue; layout:stack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan agent generates this once, and downstream skills extract values from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;theme:&lt;/code&gt; → presets the visual theme (Noir, Classic, Fresh, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;look:&lt;/code&gt; → natural language visual description for the style AI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;layout:&lt;/code&gt; → page flow type (flat, stack, cube)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule: if a task has &lt;code&gt;lookAndFeel&lt;/code&gt;, downstream skills must use it. If the connect task says &lt;code&gt;theme:Noir&lt;/code&gt;, the report task cannot override it with &lt;code&gt;theme:Fresh&lt;/code&gt;. The plan agent enforces this by generating consistent lookAndFeel values across connect/report/expert tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constrain the AI.&lt;/strong&gt; Free-form planning sounds flexible but produces garbage. A decision matrix with 6 rows covers 95% of use cases and eliminates phantom modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-containment is non-negotiable.&lt;/strong&gt; Cross-task references break when tasks execute independently. Validate and reject them early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't trust the AI with internal IDs.&lt;/strong&gt; Template names, page IDs, and other internal identifiers should be computed by the engine, not generated by the AI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always have a runtime fallback.&lt;/strong&gt; If the plan agent forgets to declare dimension count, the execution engine should be able to count them from the in-memory scale data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Next: [Form Module — Scene-aware field types and inline scoring]&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>[1/10] How I Built an AI-Powered Assessment-to-Report Pipeline with 6 Modular Skills</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Mon, 06 Jul 2026 04:23:29 +0000</pubDate>
      <link>https://dev.to/keofung/110-how-i-built-an-ai-powered-assessment-to-report-pipeline-with-6-modular-skills-1g82</link>
      <guid>https://dev.to/keofung/110-how-i-built-an-ai-powered-assessment-to-report-pipeline-with-6-modular-skills-1g82</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series: Building a Modular Assessment Engine (1/10)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if a user could type one sentence — "build a workplace stress assessment" — and get a complete, publishable assessment application with a scoring engine, a styled UI, a personalized PDF report, and an AI expert assistant?&lt;/p&gt;

&lt;p&gt;That's what I spent the last year building. This series breaks down the architecture, module by module, including the bugs that nearly killed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One-Sentence Input Problem
&lt;/h2&gt;

&lt;p&gt;The core challenge: turn natural language into a fully functional application. Not a template. Not a wizard. An actual app with form fields, scoring logic, visual design, and a PDF report — all generated, all working.&lt;/p&gt;

&lt;p&gt;The solution I landed on is a &lt;strong&gt;modular skill pipeline&lt;/strong&gt;. Six independent modules, each with its own AI prompt specification (SKILL.md), each generating CLI commands that mutate a shared domain model. A planning agent decides which modules to activate and in what order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Six Modules
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input → [Plan Agent] → Task Sequence → Execution Engine → Published App

Modules:
  1. form    — Form field design (question types, scoring, grouping)
  2. scale   — Dimension scoring (factors, ranges, direction handling)
  3. connect — UI pages (cover, main form, final page, visual style)
  4. report  — PDF report (page templates, widgets, score logic)
  5. expert  — AI assistant (role, welcome message, behavior boundaries)
  6. share   — Publishing (access control, permissions, validity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;SKILL.md&lt;/strong&gt; file that serves as the AI's system prompt (commands, rules, examples)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CLI command set&lt;/strong&gt; for mutating the domain model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;References&lt;/strong&gt; — runtime data the AI reads before generating commands (current form fields, scale dimensions, report pages, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI doesn't write code. It writes CLI commands. The execution engine runs them against the domain model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan Agent
&lt;/h2&gt;

&lt;p&gt;The plan agent is the brain. It takes user input and outputs a YAML task list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;planType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assessment&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Workplace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Stress&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Assessment'&lt;/span&gt;
&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;form&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Design&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;form'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Scene: assessment. 3 dimensions: Workload (5 questions),&lt;/span&gt;
      &lt;span class="s"&gt;Role Stress (4 questions), Social Support (4 questions).&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scale&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Configure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scoring&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dimensions'&lt;/span&gt;
    &lt;span class="na"&gt;knowledge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;3 dimensions, all negative direction (high score = worse).&lt;/span&gt;
      &lt;span class="s"&gt;3-tier ranges per dimension.&lt;/span&gt;
  &lt;span class="c1"&gt;# ... connect, report, share follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;knowledge&lt;/code&gt; field is critical: it's the &lt;strong&gt;only context&lt;/strong&gt; each skill receives during execution. Task A's knowledge doesn't leak into Task B. This forces each knowledge block to be self-contained — dimensions, question counts, scoring rules, everything.&lt;/p&gt;

&lt;p&gt;This was a hard lesson. Early versions had tasks referencing "same as above" or "see form task." The execution engine runs tasks independently, so those references resolved to nothing. Half the generated apps were broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Execution Engine
&lt;/h2&gt;

&lt;p&gt;Execution is &lt;strong&gt;SSE-streamed&lt;/strong&gt;. For each task:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the skill's SKILL.md as system prompt&lt;/li&gt;
&lt;li&gt;Inject the task's &lt;code&gt;knowledge&lt;/code&gt; + runtime references (current app state)&lt;/li&gt;
&lt;li&gt;Start an SSE stream to the AI model&lt;/li&gt;
&lt;li&gt;Each line the AI generates is a CLI command — execute it immediately&lt;/li&gt;
&lt;li&gt;Feed the result back to the execution context
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Task: form
  AI generates:  assess form add &lt;span class="nt"&gt;--id&lt;/span&gt; name &lt;span class="nt"&gt;--type&lt;/span&gt; input &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
  Engine:        → executes, field created
  AI generates:  assess form add &lt;span class="nt"&gt;--id&lt;/span&gt; workload_1 &lt;span class="nt"&gt;--type&lt;/span&gt; scale &lt;span class="nt"&gt;--min&lt;/span&gt; 1 &lt;span class="nt"&gt;--max&lt;/span&gt; 5 ...
  Engine:        → executes, field created
  ...
Task: scale
  AI generates:  assess scale add &lt;span class="nt"&gt;--id&lt;/span&gt; workload &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Workload"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt; ...
  Engine:        → executes, dimension created
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The streaming approach means commands execute as they're generated, not after the full response. This cut generation time by ~40% for typical apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Domain Model
&lt;/h2&gt;

&lt;p&gt;Underneath, there's a Java domain model with a builder pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flower (root)
  └── Model
       ├── Factor
       │    └── Scale[] (dimensions)
       │         └── Data[] (score ranges)
       ├── Form (fields)
       │    └── Field[]
       ├── Connect (pages)
       │    ├── CoverPage[]
       │    ├── MainPage
       │    └── FinalPage[]
       ├── Report (pages)
       │    └── ReportPage[]
       │         └── Widget[]
       └── Expert (AI assistant config)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each CLI command mutates this tree. The &lt;code&gt;SessionContext&lt;/code&gt; holds the in-memory state during execution, then persists everything in one batch at the end. This avoids N separate database writes during a 50-command form task.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Agents
&lt;/h2&gt;

&lt;p&gt;There are actually two AI agents in the system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AssessAgent&lt;/strong&gt; — the one-shot generator. Takes user input → plans → executes → done. Used when creating a new app from scratch. Two phases: &lt;code&gt;plan()&lt;/code&gt; and &lt;code&gt;execute()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BuilderAgent&lt;/strong&gt; — the conversational editor. Used when modifying an existing app. Four phases: Think (analyze current state) → Plan (generate PATCH tasks) → Execute (stream commands) → Reflect (AI verifies results and responds to user).&lt;/p&gt;

&lt;p&gt;Same skill SKILL.md files power both agents. The difference is context: AssessAgent starts from empty, BuilderAgent starts from a snapshot of the existing app.&lt;/p&gt;

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

&lt;p&gt;This series will deep-dive each module and the infrastructure around them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;[This post]&lt;/strong&gt; Architecture overview&lt;/li&gt;
&lt;li&gt;The Planning Engine: YAML task routing and knowledge self-containment&lt;/li&gt;
&lt;li&gt;Form Module: Scene-aware field types and inline scoring&lt;/li&gt;
&lt;li&gt;Scale Module: Solving the dimension direction problem&lt;/li&gt;
&lt;li&gt;Connect Module: AI-driven cover pages and visual styles&lt;/li&gt;
&lt;li&gt;Report Module: Page-templated PDF generation&lt;/li&gt;
&lt;li&gt;Expert Module: Giving AI assistants personality and boundaries&lt;/li&gt;
&lt;li&gt;The One-Click Pipeline: How one SSE stream orchestrates everything&lt;/li&gt;
&lt;li&gt;SSE Reliability: Three rounds of fixing event loss&lt;/li&gt;
&lt;li&gt;PDF Rendering War: SVG, emoji, and the iText NPE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each post includes real code, real bugs, and the solutions that survived production.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This series documents the architecture of an assessment platform. I'm sharing the engineering decisions and failures — not pitching a product. If you're building something similar (AI-driven code generation, modular skill systems, PDF rendering pipelines), hopefully this saves you some pain.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Built an AI Assessment Tool to Productize My Consulting Expertise (Without Coding)</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Mon, 25 May 2026 10:30:53 +0000</pubDate>
      <link>https://dev.to/keofung/how-i-built-an-ai-assessment-tool-to-productize-my-consulting-expertise-without-coding-1bmb</link>
      <guid>https://dev.to/keofung/how-i-built-an-ai-assessment-tool-to-productize-my-consulting-expertise-without-coding-1bmb</guid>
      <description>&lt;p&gt;If you're a consultant, coach, or domain expert, you know the drill: your income is strictly tied to your hours. You can only take on so many clients, and scaling means hiring more people, which dilutes your margins and your unique value.&lt;/p&gt;

&lt;p&gt;I hit this exact ceiling a few months ago. I had a proven diagnostic framework that worked wonders for my clients, but I was manually running every single assessment. It was exhausting. I needed a way to productize my knowledge—to turn my brain into a scalable asset that could generate leads and deliver value 24/7.&lt;/p&gt;

&lt;p&gt;The problem? I'm not a developer. Building a custom web app to handle complex conditional logic and generate personalized PDF reports was going to cost thousands of dollars and take months.&lt;/p&gt;

&lt;p&gt;That's when I discovered a no-code approach using AI.&lt;/p&gt;

&lt;p&gt;The "Time-for-Money" Trap&lt;/p&gt;

&lt;p&gt;Before we dive into the solution, let's look at why traditional consulting is so hard to scale:&lt;/p&gt;

&lt;p&gt;1.The Lead Gen Struggle: Giving away static PDFs or whitepapers doesn't work anymore. People want immediate, personalized insights.&lt;/p&gt;

&lt;p&gt;2.The "Prove It" Problem: It's hard to demonstrate the value of your expertise before a client signs the contract.&lt;/p&gt;

&lt;p&gt;3.The Delivery Bottleneck: Even if you get 100 leads, you can't manually assess all of them.&lt;/p&gt;

&lt;p&gt;I needed a "hook" that was highly engaging, automated, and actually useful to the user. An interactive assessment was the perfect answer.&lt;/p&gt;

&lt;p&gt;The Solution: AI-Powered Forms&lt;/p&gt;

&lt;p&gt;I started looking for form builders, but standard tools like Typeform or Google Forms were too rigid. They couldn't handle the nuanced scoring logic my framework required, and they definitely couldn't generate a personalized, multi-page report at the end.&lt;/p&gt;

&lt;p&gt;I eventually found FormLM, a platform specifically built for this exact use case. It's an AI Form Builder designed to turn professional knowledge into automated assessment services.&lt;/p&gt;

&lt;p&gt;Here is how I set it up:&lt;/p&gt;

&lt;p&gt;Step 1: Digitizing the Framework&lt;/p&gt;

&lt;p&gt;Instead of writing complex if/then statements, I used FormLM's AI builder to input my diagnostic criteria. The AI helped structure the questions to ensure they flowed logically and captured the right data points.&lt;/p&gt;

&lt;p&gt;Step 2: Designing the Personalized Report&lt;/p&gt;

&lt;p&gt;This is where the magic happens. FormLM takes the user's inputs and uses AI to generate a highly personalized assessment report. It doesn't just say "You scored a 7/10." It provides specific, contextual feedback based on my methodology.&lt;/p&gt;

&lt;p&gt;Step 3: The AI Consulting Assistant&lt;/p&gt;

&lt;p&gt;I also enabled their AI Consulting feature. Now, after a user gets their report, they can chat with an AI assistant that is trained exclusively on my knowledge base. It answers follow-up questions and naturally guides them toward booking a paid consultation with me.&lt;/p&gt;

&lt;p&gt;The Results&lt;/p&gt;

&lt;p&gt;The impact on my business was immediate:&lt;/p&gt;

&lt;p&gt;•Lead Quality Skyrocketed: Instead of generic email captures, I now get detailed profiles of my prospects before we even speak.&lt;/p&gt;

&lt;p&gt;•Instant Trust: Because the automated report delivers real value, prospects enter the sales call already trusting my expertise.&lt;/p&gt;

&lt;p&gt;•True Scalability: My framework is now assessing dozens of companies a week while I sleep.&lt;/p&gt;

&lt;p&gt;How You Can Do It Too&lt;/p&gt;

&lt;p&gt;If you have a proven methodology—whether it's an "Enterprise Digital Maturity" diagnostic, a "Marketing ROI Calculator," or an HR "Culture Fit" assessment—you are sitting on a goldmine.&lt;/p&gt;

&lt;p&gt;You don't need to learn React or hire an agency to build a SaaS. You just need to translate your logic into an automated workflow.&lt;/p&gt;

&lt;p&gt;If you want to break free from the billable-hour trap, I highly recommend checking out FormLM. It’s the fastest way I've found to turn your expertise into a 24/7 product.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>productivity</category>
      <category>ai</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>FormLM</title>
      <dc:creator>Keo Fung | FormLM</dc:creator>
      <pubDate>Sun, 24 May 2026 09:44:28 +0000</pubDate>
      <link>https://dev.to/keofung/formlm-10li</link>
      <guid>https://dev.to/keofung/formlm-10li</guid>
      <description>&lt;p&gt;FormLM is the assessment platform built for people who sell expertise — consultants, coaches, trainers, and advisors who know their craft inside out but are tired of delivering the same insights one hour at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you actually get done
&lt;/h3&gt;

&lt;p&gt;Create evaluation scales, application forms, and self-assessment modules in one place. Respondents fill them out; FormLM handles the data collection, runs the analysis, and produces a finished report. Whether it's a business health check, a competency review, a qualification application, or a client diagnostic — you build the form once, and it runs itself from there.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;The pipeline is &lt;strong&gt;Form → Scale → Connect → Report → Expert&lt;/strong&gt;. Design a smart form, attach your professional scoring model, connect data across channels, auto-generate assessment reports, and layer in expert logic for follow-up guidance. Every step lives in the same system — no jumping between tools, no copy-pasting results into a Word doc at midnight.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why people switch
&lt;/h3&gt;

&lt;p&gt;Building an evaluation scale or a formal assessment application used to mean days of setup and a pile of manual work. With FormLM, you go from idea to a fully functional assessment in minutes. Less time producing deliverables, more time doing work that actually requires you. Your expertise stops being a bottleneck and starts being a product.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
