DEV Community

Keo Fung | FormLM
Keo Fung | FormLM

Posted on

[1/10] How I Built an AI-Powered Assessment-to-Report Pipeline with 6 Modular Skills

Series: Building a Modular Assessment Engine (1/10)

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?

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.

The One-Sentence Input Problem

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.

The solution I landed on is a modular skill pipeline. 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.

The Six Modules

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)
Enter fullscreen mode Exit fullscreen mode

Each module has:

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

The AI doesn't write code. It writes CLI commands. The execution engine runs them against the domain model.

The Plan Agent

The plan agent is the brain. It takes user input and outputs a YAML task list:

planType: assessment
description: 'Workplace Stress Assessment'
tasks:
  - seq: 1
    skill: form
    title: 'Design assessment form'
    knowledge: |
      Scene: assessment. 3 dimensions: Workload (5 questions),
      Role Stress (4 questions), Social Support (4 questions).
  - seq: 2
    skill: scale
    title: 'Configure scoring dimensions'
    knowledge: |
      3 dimensions, all negative direction (high score = worse).
      3-tier ranges per dimension.
  # ... connect, report, share follow
Enter fullscreen mode Exit fullscreen mode

The knowledge field is critical: it's the only context 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.

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.

The Execution Engine

Execution is SSE-streamed. For each task:

  1. Load the skill's SKILL.md as system prompt
  2. Inject the task's knowledge + runtime references (current app state)
  3. Start an SSE stream to the AI model
  4. Each line the AI generates is a CLI command — execute it immediately
  5. Feed the result back to the execution context
Task: form
  AI generates:  assess form add --id name --type input --title "Your Name"
  Engine:        → executes, field created
  AI generates:  assess form add --id workload_1 --type scale --min 1 --max 5 ...
  Engine:        → executes, field created
  ...
Task: scale
  AI generates:  assess scale add --id workload --name "Workload" --format sum ...
  Engine:        → executes, dimension created
  ...
Enter fullscreen mode Exit fullscreen mode

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

The Domain Model

Underneath, there's a Java domain model with a builder pattern:

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)
Enter fullscreen mode Exit fullscreen mode

Each CLI command mutates this tree. The SessionContext 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.

The Two Agents

There are actually two AI agents in the system:

AssessAgent — the one-shot generator. Takes user input → plans → executes → done. Used when creating a new app from scratch. Two phases: plan() and execute().

BuilderAgent — 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).

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.

What's Next

This series will deep-dive each module and the infrastructure around them:

  1. [This post] Architecture overview
  2. The Planning Engine: YAML task routing and knowledge self-containment
  3. Form Module: Scene-aware field types and inline scoring
  4. Scale Module: Solving the dimension direction problem
  5. Connect Module: AI-driven cover pages and visual styles
  6. Report Module: Page-templated PDF generation
  7. Expert Module: Giving AI assistants personality and boundaries
  8. The One-Click Pipeline: How one SSE stream orchestrates everything
  9. SSE Reliability: Three rounds of fixing event loss
  10. PDF Rendering War: SVG, emoji, and the iText NPE

Each post includes real code, real bugs, and the solutions that survived production.


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.

Top comments (0)