r mcp-rune 0.1.0
SECTION II · GUIDE 05 OF 19
Reading
11 min
Topic
architecture
Spec
v0.1.0-alpha
Source
prompt-derivation-framework-guide.md

Prompt Derivation Framework Guide

A 5-layer architecture for generating prompt documentation from model and prompt configuration, eliminating manual duplication and ensuring consistency.

Table of Contents

Architecture Overview

┌──────────────────────────────────────────────────────────────────┐
│ Layer 5: BEHAVIORAL — generateStatefulGuidanceInstructions()    │
│ (BasePrompt) Turn-taking, validation, mode selection            │
├──────────────────────────────────────────────────────────────────┤
│ Layer 4: ASSEMBLY — PromptContentGenerator.build()              │
│ Composes all layers into final promptContent                    │
├──────────────────────────────────────────────────────────────────┤
│ Layer 3: SECTION DOCS — PromptContentGenerator + BasePrompt     │
│ Per-section field tables, enum tables, content notes            │
├──────────────────────────────────────────────────────────────────┤
│ Layer 2: GROUPING — sections + fieldGroups                      │
│ (BasePrompt static config) Workflow structure, field org        │
├──────────────────────────────────────────────────────────────────┤
│ Layer 1: SCHEMA — derivePromptSchema()                          │
│ (schema-derivation.js) fieldDefinitions from model config       │
└──────────────────────────────────────────────────────────────────┘

Data flows bottom-up: Model config → field definitions → grouped sections → assembled documentation → behavioral instructions.

Layer 1: Schema Derivation

File: lib/mcp/prompts/schema-derivation.js

Generates fieldDefinitions from model’s attributes. This is the foundation — all field metadata comes from the model.

import { derivePromptSchema } from '#src/mcp/prompts/schema-derivation.js'
import { Activity } from '../models/index.js'

static {
  const schema = derivePromptSchema(Activity, {
    fieldGroups: this.fieldGroups,
    fieldOverrides: {
      // Override/extend fields from model
      theme_id: { required: true }
    },
    promptFields: {
      // Prompt-only fields not in model
      book_ids: { name: 'book_ids', type: 'array', required: false }
    }
  })

  this.fieldGroups = schema.fieldGroups
  this.fieldDefinitions = schema.fieldDefinitions
}

Key principle: The model’s attributes is the single source of truth. derivePromptSchema() reads type, description, examples, enumValues, enumDescriptions, default, validation, conditional, and required from the model and assembles them into fieldDefinitions.

Layer 2: Grouping

File: Prompt class static properties

Detailed reference: Sections & Field Groups guide. This section presents grouping as a layer of the derivation pipeline; the linked guide is the canonical reference for the two structures themselves.

Two complementary structures organize fields:

Sections (User-facing)

static sections = {
  classification: {
    title: 'Classification',
    description: 'Theme and category',
    required: true,
    groups: ['classification'],
    content: {
      intro: 'Classification determines how activities are organized.',
      notes: ['Use find_records to look up themes']
    }
  }
}

FieldGroups (Validation)

static fieldGroups = {
  classification: {
    fields: ['theme_id', 'category_id'],
    context: 'Classification',
    required: true
  }
}

Section content enrichment: The content.intro and content.notes properties are automatically included in generated section documentation (Layer 3). Use these to add domain-specific context without writing custom section generator methods:

  • content.intro (string) — Rendered before the field table. Supports full markdown.
  • content.notes (string[]) — Rendered as a bullet list after the field table and enum tables.
  • askPrompt (string) — Custom “Ask the user: …” prompt at the end of the section.

Key principle: Prefer content.intro/content.notes over customSections overrides in allSections(). This keeps domain content in configuration while the framework auto-generates field tables and enum tables alongside it.

Layer 3: Section Documentation

Files: lib/mcp/prompts/base-prompt.js, lib/mcp/prompts/prompt-content-generator.js

Generates per-section documentation from config. Includes:

  • Field tables (name, required, description)
  • Enum value tables (from enumDescriptions in model config)
  • Section intro text (from content.intro)
  • Section notes (from content.notes)
  • “Ask the user” prompts
  • Validation reminders

Atomic Helpers (BasePrompt static methods)

MethodInputOutput
generateSectionDocumentation(group, num, model)fieldGroup name, section number, model nameComplete section doc with field table, enum tables, ask prompt
generateEnumTable(fieldName)Field name with enumValuesMarkdown table of enum values with descriptions
generateAttributeReferenceFromConfig()(uses this.fieldDefinitions)Full attribute reference table
generateSummaryTemplate(modelName)Model nameStandard summary/confirmation section

Enum Tables

When a model field has enumDescriptions, enum tables are automatically generated:

// In model:
static attributes = {
  status: {
    type: 'enum',
    enumValues: ['planned', 'active', 'paused', 'completed', 'archived'],
    default: 'planned',
    enumDescriptions: {
      planned: 'Not yet started',
      active: 'Currently in progress',
      paused: 'Temporarily on hold',
      completed: 'Finished',
      archived: 'No longer relevant'
    }
  }
}

Generated output:

**`status` values:**
| Value | Description |
|-------|-------------|
| `"planned"` | Not yet started **(default)** |
| `"active"` | Currently in progress |
| `"paused"` | Temporarily on hold |
| `"completed"` | Finished |
| `"archived"` | No longer relevant |

Layer 4: Assembly Pipeline

File: lib/mcp/prompts/prompt-content-generator.js

The PromptContentGenerator builder composes all layers into final promptContent.

get promptContent() {
  return PromptContentGenerator.for(ActivityPrompt, 'activity')
    .add(`# Activity Creation Guide

## What is an Activity?
Custom intro text...`)
    .standard()           // flowDiagram → guidance → allSections → summary
    .add(this.generateToolUsageSection())  // Custom tool usage
    .attributeReference() // Layer 3: attribute reference table
    .build()              // Join with '\n\n---\n\n'
}

Parts are joined with \n\n---\n\n (horizontal rules) by default.

Layer 5: Behavioral

File: lib/mcp/prompts/base-prompt.jsgenerateStatefulGuidanceInstructions()

Only applies to stateful prompts. Generates:

  • Mode selection (guided vs quick)
  • Turn-taking enforcement rules
  • Section-by-section validation requirements
  • Forbidden/correct behavior patterns

Accessed via .guidance() in the builder.

PromptContentGenerator API

Factory

PromptContentGenerator.for(PromptClass, 'model_name')

Builder Methods

MethodDescriptionUse With
.add(content)Add custom markdown contentAll strategies
.standard(options?)Canonical: flowDiagram → guidance → beforeSections → allSections → summaryAll
.guidance()Stateful guidance instructions (Layer 5)Stateful only
.section(groupName, num, options)Single section documentationStateful
.allSections({ skip, customSections })All sections from configStateful
.summary()Standard summary/confirmation templateStateful
.attributeReference()Auto-generated attribute tableAll strategies
.build(separator)Join parts (default: \n\n---\n\n)All

.allSections() Options

.allSections({
  skip: ['content'],  // Skip sections handled by custom .add() calls
  customSections: {
    // Override specific sections with custom generators
    resources: (sectionNum) => `## SECTION ${sectionNum}: Resources\n...custom content...`
  }
})

Migration Guide

Before (manual documentation)

get promptContent() {
  return `
# My Guide
...intro...

| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | The name |        ← Hardcoded, will drift from model
| type | No | The type |

## Summary
...manual summary...

## Attribute Reference
${this.generateAttributeReference()}  ← Custom method per prompt
`
}

After (framework)

import { PromptContentGenerator } from '#src/mcp/prompts/prompt-content-generator.js'

get promptContent() {
  return PromptContentGenerator.for(MyPrompt, 'my_model')
    .add(`# My Guide\n\n...intro...`)
    .standard()
    .add(this.generateToolUsageSection())
    .attributeReference()  // One line replaces 20+ lines
    .build()
}

Migration Steps

  1. Add import { PromptContentGenerator } from '#src/mcp/prompts/prompt-content-generator.js'
  2. Replace promptContent getter with builder pipeline
  3. Remove generateAttributeReference() → replaced by .attributeReference()
  4. Remove generateSummarySection() → replaced by .summary() (stateful)
  5. Keep domain-specific methods (tool usage, custom sections) as .add() calls
  6. Update tests if they check for specific format strings

Content Categories

When migrating, classify each piece of content:

CategoryDescriptionAction
A: Auto-generatableSummary templates, attribute referencesReplace with .summary(), .attributeReference()
B: Config-generatableSection documentation, enum tablesUse .allSections() or .section(), enrich content.notes
C: CustomIntro text, tool usage, domain-specific logicKeep as .add() calls

Rule of thumb: If the content depends only on fieldDefinitions, fieldGroups, or sections, it’s auto-generatable. If it requires runtime state or domain knowledge, keep it as .add().