Update the attempt counter example in SPEC.yaml to use substitution-only
template syntax instead of Jinja2 control structures ({% if %}).
The AI can naturally understand attempt context from {{current_attempt}},
{{max_attempts}}, and {{attempts_remaining}} variables without needing
conditional logic in the template itself.
This aligns with the substitution-only template system where logic lives
in scripts and templates only display pre-computed values.
1147 lines
42 KiB
YAML
1147 lines
42 KiB
YAML
# ==============================================================================
|
|
# OpenCompletion Activity YAML Specification
|
|
# ==============================================================================
|
|
# This document defines ALL supported mechanics for creating educational
|
|
# activities in the OpenCompletion system.
|
|
#
|
|
# Version: 1.0
|
|
# Last Updated: 2025-01-10
|
|
# ==============================================================================
|
|
|
|
# ==============================================================================
|
|
# ACTIVITY ROOT LEVEL
|
|
# ==============================================================================
|
|
# These fields apply to the entire activity
|
|
|
|
# Maximum number of times a user can attempt each step before auto-advancing
|
|
# Default: 3
|
|
# Optional
|
|
default_max_attempts_per_step: 3
|
|
|
|
# Model to use for categorizing user responses into buckets
|
|
# Default: "MODEL_1" (Hermes-3-Llama-3.1-8B)
|
|
# Optional
|
|
classifier_model: "MODEL_1"
|
|
|
|
# Model to use for generating AI feedback
|
|
# Default: "MODEL_1" (Hermes-3-Llama-3.1-8B)
|
|
# Optional
|
|
# Tip: Use faster models (MODEL_1) for classification, specialized models (MODEL_3) for feedback
|
|
feedback_model: "MODEL_1"
|
|
|
|
# Global rubric for evaluating student responses across all steps
|
|
# This provides consistent evaluation criteria
|
|
# Optional
|
|
tokens_for_ai_rubric: |
|
|
You are helping students learn about [TOPIC].
|
|
|
|
Evaluate their responses based on:
|
|
- Understanding of core concepts
|
|
- Clarity of explanation
|
|
- Critical thinking demonstrated
|
|
|
|
Be encouraging and constructive!
|
|
|
|
# ==============================================================================
|
|
# SECTIONS
|
|
# ==============================================================================
|
|
# Activities are organized into sections, which contain steps
|
|
# Required: At least one section
|
|
|
|
sections:
|
|
# Each section must have a unique section_id
|
|
- section_id: "introduction" # REQUIRED - Unique identifier
|
|
title: "Getting Started" # REQUIRED - Human-readable title
|
|
|
|
# Steps are the individual interactions within a section
|
|
steps:
|
|
# ========================================================================
|
|
# STEP TYPE 1: CONTENT-ONLY STEP
|
|
# ========================================================================
|
|
# Displays information and automatically advances
|
|
# No user interaction required
|
|
|
|
- step_id: "welcome" # REQUIRED - Unique within this section
|
|
title: "Welcome" # REQUIRED - Human-readable title
|
|
|
|
# Content blocks are displayed to the user
|
|
# Supports markdown formatting
|
|
content_blocks: # REQUIRED for content-only steps
|
|
- "# Welcome to the Activity! 🎉"
|
|
- ""
|
|
- "This activity will teach you about [TOPIC]."
|
|
- ""
|
|
- "**What you'll learn:**"
|
|
- "- Concept 1"
|
|
- "- Concept 2"
|
|
- "- Concept 3"
|
|
- ""
|
|
- "Let's get started!"
|
|
|
|
# Content-only steps automatically advance to the next step
|
|
# No question, buckets, or transitions needed
|
|
|
|
# ========================================================================
|
|
# STEP TYPE 2: QUESTION STEP
|
|
# ========================================================================
|
|
# Interactive step that requires user response
|
|
|
|
- step_id: "question_example"
|
|
title: "Your First Question"
|
|
|
|
# Optional: Content blocks can appear before the question
|
|
content_blocks:
|
|
- "## Background Information"
|
|
- "Before we ask the question, here's some context..."
|
|
|
|
# The question asked to the user
|
|
question: "What is your name?" # REQUIRED for question steps
|
|
|
|
# Instructions for the AI on how to categorize the user's response
|
|
# The AI will read this and place the response into one of the buckets
|
|
tokens_for_ai: | # REQUIRED for question steps
|
|
Categorize the user's response:
|
|
|
|
- name_provided: They gave a name (any name is acceptable)
|
|
- set_language: They want to change language preference
|
|
- off_topic: Their response is unrelated to the question
|
|
|
|
Be generous in accepting names - nicknames, full names, etc.
|
|
|
|
# Instructions for generating feedback after categorization
|
|
# This is used when creating ai_feedback in transitions
|
|
feedback_tokens_for_ai: | # Optional but recommended
|
|
Welcome the user by their name warmly!
|
|
Make them feel comfortable and ready to learn.
|
|
|
|
Example: "Welcome, [name]! Great to have you here!"
|
|
|
|
# List of possible categories (buckets) for user responses
|
|
# Every response will be categorized into one of these
|
|
buckets: # REQUIRED for question steps
|
|
- name_provided
|
|
- set_language
|
|
- off_topic
|
|
- emergency # Random bucket - also in buckets list
|
|
- surprise # Random bucket - also in buckets list
|
|
- bonus # Random bucket - also in buckets list
|
|
|
|
# ======================================================================
|
|
# RANDOM BUCKETS (Optional)
|
|
# ======================================================================
|
|
# Probabilistic events that can trigger alongside user responses
|
|
# Random rolls happen BEFORE categorization
|
|
# Multiple random buckets can trigger simultaneously
|
|
|
|
random_buckets: # Optional
|
|
# Each random bucket must also appear in the main buckets list above
|
|
emergency:
|
|
probability: 0.05 # 5% chance (0.0 to 1.0)
|
|
|
|
surprise:
|
|
probability: 0.10 # 10% chance
|
|
|
|
bonus:
|
|
probability: 0.03 # 3% chance
|
|
|
|
# Processing Order:
|
|
# 1. Random buckets rolled
|
|
# 2. User response categorized
|
|
# 3. User's bucket processed FIRST
|
|
# 4. Random buckets processed in order they triggered
|
|
# 5. Metadata accumulates across all transitions
|
|
# 6. Last transition's navigation wins
|
|
|
|
# ======================================================================
|
|
# TRANSITIONS
|
|
# ======================================================================
|
|
# Define what happens for each bucket
|
|
# REQUIRED: One transition per bucket (including random buckets)
|
|
|
|
transitions:
|
|
# ==================================================================
|
|
# TRANSITION STRUCTURE
|
|
# ==================================================================
|
|
# Each bucket name maps to a transition configuration
|
|
|
|
name_provided:
|
|
# ----------------------------------------------------------------
|
|
# CONTENT BLOCKS (Optional)
|
|
# Static text displayed immediately
|
|
# ----------------------------------------------------------------
|
|
content_blocks:
|
|
- "Great! Let's continue."
|
|
|
|
# ----------------------------------------------------------------
|
|
# AI FEEDBACK (Optional)
|
|
# Dynamic feedback generated by the AI
|
|
# Uses feedback_tokens_for_ai from the step
|
|
# ----------------------------------------------------------------
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
Generate personalized feedback based on their response.
|
|
Reference their specific answer to show you're paying attention.
|
|
Be encouraging!
|
|
|
|
# ----------------------------------------------------------------
|
|
# METADATA OPERATIONS (Optional)
|
|
# Modify the persistent metadata that follows the user
|
|
# ----------------------------------------------------------------
|
|
|
|
# ADD or UPDATE metadata keys
|
|
metadata_add:
|
|
# Store the exact user response
|
|
user_name: "the-users-response"
|
|
|
|
# Numeric increment: n+5 means "add 5 to existing value (or 0)"
|
|
score: "n+5"
|
|
|
|
# Numeric decrement: n-3 means "subtract 3 from existing value"
|
|
lives: "n-3"
|
|
|
|
# String concatenation: n+,value means "append value to comma-separated list"
|
|
achievements: "n+,first_question"
|
|
# If achievements was "started", becomes "started,first_question"
|
|
# If achievements was empty, becomes "first_question"
|
|
|
|
# String removal: n-,value means "remove value from comma-separated list"
|
|
# pending_tasks: "n-,intro" # Removes "intro" from list
|
|
|
|
# Random numeric increment: n+random(1,10) adds random number between 1 and 10
|
|
bonus_points: "n+random(1,10)"
|
|
|
|
# Static value
|
|
step_completed: "true"
|
|
|
|
# Timestamp or any string
|
|
last_active: "2025-01-10"
|
|
|
|
# TEMPORARY metadata (removed at end of step)
|
|
# Useful for one-time values that don't persist
|
|
metadata_tmp_add:
|
|
temp_hint: "Remember this for the next question!"
|
|
temp_score: "n+2" # All same operations as metadata_add work here
|
|
|
|
# REMOVE specific metadata keys
|
|
metadata_remove:
|
|
- old_key
|
|
- another_key
|
|
# Or single key:
|
|
# metadata_remove: "single_key"
|
|
|
|
# CLEAR all metadata (use with caution!)
|
|
metadata_clear: true
|
|
|
|
# RANDOM metadata - pick ONE random key-value pair
|
|
metadata_random:
|
|
random_event: "event_a" # One of these will be chosen
|
|
random_event: "event_b"
|
|
random_event: "event_c"
|
|
|
|
# TEMPORARY random metadata - pick from list, remove at end of step
|
|
metadata_tmp_random:
|
|
dice_roll: [1, 2, 3, 4, 5, 6] # One value chosen randomly
|
|
color_choice: ["red", "blue", "green"]
|
|
|
|
# ----------------------------------------------------------------
|
|
# METADATA CONDITIONS (Optional)
|
|
# Only execute this transition if conditions are met
|
|
# ----------------------------------------------------------------
|
|
metadata_conditions:
|
|
level: 5 # metadata.level must equal 5
|
|
has_key: "yes" # metadata.has_key must equal "yes"
|
|
# All conditions must be true (AND logic)
|
|
|
|
# ----------------------------------------------------------------
|
|
# METADATA FEEDBACK FILTER (Optional)
|
|
# Only show AI feedback if specific metadata keys exist
|
|
# ----------------------------------------------------------------
|
|
metadata_feedback_filter:
|
|
- "achievement_unlocked"
|
|
- "bonus_available"
|
|
# AI feedback only generated if these keys are present in metadata
|
|
|
|
# ----------------------------------------------------------------
|
|
# PROCESSING SCRIPT (Optional)
|
|
# Execute Python code to perform complex logic
|
|
# ----------------------------------------------------------------
|
|
# Note: processing_script is defined at STEP level, not transition level
|
|
# Use run_processing_script: true to execute it for this transition
|
|
run_processing_script: true
|
|
|
|
# ----------------------------------------------------------------
|
|
# NAVIGATION (Optional)
|
|
# Where to go next
|
|
# ----------------------------------------------------------------
|
|
next_section_and_step: "introduction:processing_example"
|
|
# Format: "section_id:step_id"
|
|
# If omitted, stays on current step (useful for retry loops)
|
|
# If ALL transitions omit this, activity terminates
|
|
|
|
# ----------------------------------------------------------------
|
|
# ATTEMPT COUNTING (Optional)
|
|
# Whether this transition counts toward max_attempts_per_step
|
|
# ----------------------------------------------------------------
|
|
counts_as_attempt: false # Default: true
|
|
# Set to false for:
|
|
# - Hints that let user retry
|
|
# - Language changes
|
|
# - Clarifying questions
|
|
# Set to true for:
|
|
# - Wrong answers
|
|
# - Correct answers
|
|
# - Progress-making choices
|
|
|
|
# ==================================================================
|
|
# SPECIAL BUCKETS
|
|
# ==================================================================
|
|
|
|
# Language change bucket (standard pattern)
|
|
set_language:
|
|
content_blocks:
|
|
- "Language preference updated."
|
|
metadata_add:
|
|
language: "the-users-response"
|
|
counts_as_attempt: false # Don't penalize language changes
|
|
next_section_and_step: "introduction:question_example" # Retry same question
|
|
|
|
# Off-topic response (retry pattern)
|
|
off_topic:
|
|
content_blocks:
|
|
- "I didn't understand that. Could you try again?"
|
|
next_section_and_step: "introduction:question_example" # Retry
|
|
# counts_as_attempt: true (default) - wrong answers count
|
|
|
|
# Random event transitions
|
|
emergency:
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
🚨 EMERGENCY EVENT!
|
|
Describe the emergency dramatically.
|
|
Show how the user handles it with their previous choice.
|
|
metadata_add:
|
|
emergencies_handled: "n+1"
|
|
random_events: "n+,emergency"
|
|
counts_as_attempt: false # Random events don't count as attempts
|
|
# No next_section_and_step - uses user's navigation
|
|
|
|
surprise:
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
✨ SURPRISE EVENT!
|
|
Something unexpected happens!
|
|
metadata_add:
|
|
surprises_encountered: "n+1"
|
|
bonus_points: "n+random(5,15)"
|
|
counts_as_attempt: false
|
|
|
|
bonus:
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
🎁 BONUS EVENT!
|
|
You earned a bonus!
|
|
metadata_add:
|
|
bonuses_collected: "n+1"
|
|
counts_as_attempt: false
|
|
|
|
# ========================================================================
|
|
# PROCESSING SCRIPTS
|
|
# ========================================================================
|
|
# Python code executed during transitions
|
|
# Defined at step level, triggered by run_processing_script: true
|
|
|
|
- step_id: "processing_example"
|
|
title: "Processing Script Demo"
|
|
question: "Enter a number:"
|
|
tokens_for_ai: "Categorize as 'number' if numeric, 'invalid' otherwise"
|
|
buckets: [number, invalid]
|
|
|
|
# Pre-script runs BEFORE categorization
|
|
# Has access to user_response in metadata
|
|
pre_script: |
|
|
# Available: metadata dict (read/write), user_response
|
|
result = {}
|
|
|
|
# Parse user input
|
|
try:
|
|
value = int(metadata.get("user_response", "0"))
|
|
result["parsed_value"] = value
|
|
result["is_even"] = value % 2 == 0
|
|
except ValueError:
|
|
result["parsed_value"] = None
|
|
result["is_even"] = False
|
|
|
|
# Return dict of values to add to metadata
|
|
return result
|
|
|
|
# Processing script runs DURING transition (if run_processing_script: true)
|
|
# Has access to user_response in metadata
|
|
processing_script: |
|
|
# Available: metadata dict (read/write)
|
|
result = {}
|
|
|
|
# Complex calculations
|
|
score = metadata.get("score", 0)
|
|
multiplier = metadata.get("multiplier", 1)
|
|
result["final_score"] = score * multiplier
|
|
|
|
# Conditional logic
|
|
if result["final_score"] > 100:
|
|
result["achievement"] = "high_scorer"
|
|
|
|
return result
|
|
|
|
transitions:
|
|
number:
|
|
run_processing_script: true # Triggers processing_script above
|
|
ai_feedback:
|
|
tokens_for_ai: "Confirm their number and show calculated results from metadata"
|
|
metadata_add:
|
|
attempts: "n+1"
|
|
next_section_and_step: "introduction:feedback_prompts_example"
|
|
|
|
invalid:
|
|
content_blocks:
|
|
- "Please enter a valid number."
|
|
next_section_and_step: "introduction:processing_example"
|
|
|
|
# ========================================================================
|
|
# FEEDBACK PROMPTS (Multi-Agent Feedback)
|
|
# ========================================================================
|
|
# New system for having multiple AI agents provide feedback
|
|
# Each agent has their own personality and perspective
|
|
|
|
- step_id: "feedback_prompts_example"
|
|
title: "Multi-Agent Feedback Demo"
|
|
question: "Design a solution to [PROBLEM]"
|
|
tokens_for_ai: |
|
|
Categorize as:
|
|
- excellent: Comprehensive, creative solution
|
|
- good: Solid solution with minor gaps
|
|
- needs_work: Incomplete or flawed
|
|
buckets: [excellent, good, needs_work]
|
|
|
|
# Define multiple feedback agents
|
|
# Each has their own name, emoji, and personality
|
|
feedback_prompts:
|
|
# Technical reviewer - focuses on implementation
|
|
- name: "Tech Lead"
|
|
emoji: "🔧"
|
|
tokens_for_ai: |
|
|
You are a senior technical architect.
|
|
Review solutions for:
|
|
- Technical feasibility
|
|
- Scalability concerns
|
|
- Implementation complexity
|
|
Be constructive but thorough.
|
|
system_prompt: |
|
|
You are a senior technical architect reviewing student solutions.
|
|
|
|
# Conditions for when this agent provides feedback
|
|
metadata_conditions:
|
|
level: "advanced" # Only for advanced students
|
|
|
|
# Buckets this agent responds to
|
|
buckets_to_respond: [excellent, good] # Skips needs_work
|
|
|
|
# Creative reviewer - focuses on innovation
|
|
- name: "Design Guru"
|
|
emoji: "🎨"
|
|
tokens_for_ai: |
|
|
You are a creative design expert.
|
|
Evaluate solutions for:
|
|
- Innovation and originality
|
|
- User experience considerations
|
|
- Aesthetic appeal
|
|
Inspire them to think outside the box!
|
|
system_prompt: |
|
|
You are a creative design expert evaluating student work.
|
|
|
|
# This agent responds to all buckets (default)
|
|
|
|
# Encouraging mentor - provides emotional support
|
|
- name: "Mentor"
|
|
emoji: "🌟"
|
|
tokens_for_ai: |
|
|
You are an encouraging mentor.
|
|
Provide:
|
|
- Emotional support
|
|
- Encouragement to continue
|
|
- Recognition of effort
|
|
Always be positive and uplifting!
|
|
system_prompt: |
|
|
You are an encouraging mentor supporting students.
|
|
|
|
# Always include this agent's feedback
|
|
always_include: true
|
|
|
|
# Legacy feedback tokens (combined with feedback_prompts if both present)
|
|
feedback_tokens_for_ai: |
|
|
Provide overall feedback on their solution.
|
|
This is combined with the multi-agent feedback.
|
|
|
|
transitions:
|
|
excellent:
|
|
# Multi-agent feedback automatically generated
|
|
# Each agent in feedback_prompts provides their perspective
|
|
metadata_add:
|
|
score: "n+10"
|
|
next_section_and_step: "advanced:coding_challenge"
|
|
|
|
good:
|
|
metadata_add:
|
|
score: "n+5"
|
|
next_section_and_step: "advanced:coding_challenge"
|
|
|
|
needs_work:
|
|
content_blocks:
|
|
- "Let's try this again with some hints..."
|
|
next_section_and_step: "introduction:feedback_prompts_example"
|
|
|
|
# ==============================================================================
|
|
# STEP-LEVEL MODEL OVERRIDES
|
|
# ==============================================================================
|
|
# Steps can override the activity-level classifier and feedback models
|
|
|
|
- section_id: "advanced"
|
|
title: "Advanced Section"
|
|
steps:
|
|
- step_id: "coding_challenge"
|
|
title: "Write Code"
|
|
|
|
# Override classifier model for this step
|
|
classifier_model: "MODEL_1" # Fast classification
|
|
|
|
# Override feedback model for this step
|
|
feedback_model: "MODEL_3" # Qwen3-Coder for code review
|
|
|
|
question: "Write a function to solve [PROBLEM]"
|
|
tokens_for_ai: "Categorize as correct/incorrect based on solution quality"
|
|
buckets: [correct, incorrect]
|
|
transitions:
|
|
correct:
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
Review their code professionally.
|
|
Provide specific feedback on:
|
|
- Code style and readability
|
|
- Algorithmic efficiency
|
|
- Edge case handling
|
|
next_section_and_step: "conclusion:goodbye_content"
|
|
incorrect:
|
|
ai_feedback:
|
|
tokens_for_ai: "Provide hints without giving away the solution"
|
|
next_section_and_step: "advanced:coding_challenge"
|
|
|
|
# ==============================================================================
|
|
# TERMINATION PATTERNS
|
|
# ==============================================================================
|
|
# Activities can terminate in several ways
|
|
|
|
- section_id: "conclusion"
|
|
title: "Wrap Up"
|
|
steps:
|
|
# ========================================================================
|
|
# TERMINATION 1: Content-Only Final Step
|
|
# ========================================================================
|
|
# Simplest termination - just display content
|
|
|
|
- step_id: "goodbye_content"
|
|
title: "Thank You!"
|
|
content_blocks:
|
|
- "# Thank You for Participating! 🎉"
|
|
- ""
|
|
- "You've completed the activity!"
|
|
- "Your final score: check metadata.score"
|
|
- ""
|
|
- "Come back anytime!"
|
|
# No question = auto-terminates
|
|
|
|
# ========================================================================
|
|
# TERMINATION 2: Final Reflection Question
|
|
# ========================================================================
|
|
# Last question with no onward navigation
|
|
|
|
- step_id: "reflection"
|
|
title: "Final Reflection"
|
|
question: "What did you learn today?"
|
|
tokens_for_ai: |
|
|
Categorize their reflection as:
|
|
- thoughtful: Deep, meaningful reflection
|
|
- brief: Short but genuine
|
|
- off_topic: Not answering the question
|
|
buckets: [thoughtful, brief, off_topic]
|
|
transitions:
|
|
thoughtful:
|
|
ai_feedback:
|
|
tokens_for_ai: "Celebrate their learning and growth!"
|
|
metadata_add:
|
|
activity_completed: "true"
|
|
# No next_section_and_step = terminates
|
|
|
|
brief:
|
|
ai_feedback:
|
|
tokens_for_ai: "Thank them for their time and effort!"
|
|
metadata_add:
|
|
activity_completed: "true"
|
|
# No next_section_and_step = terminates
|
|
|
|
off_topic:
|
|
content_blocks:
|
|
- "Please reflect on what you learned in this activity."
|
|
next_section_and_step: "conclusion:reflection" # Retry
|
|
|
|
# ========================================================================
|
|
# TERMINATION 3: Explicit Exit Path
|
|
# ========================================================================
|
|
# Provide clear exit option
|
|
|
|
- step_id: "play_again"
|
|
title: "Continue?"
|
|
question: "Would you like to play again or exit?"
|
|
tokens_for_ai: "Categorize as 'again' or 'exit'"
|
|
buckets: [again, exit]
|
|
transitions:
|
|
again:
|
|
metadata_clear: true # Reset game state
|
|
next_section_and_step: "introduction:welcome" # Restart
|
|
|
|
exit:
|
|
next_section_and_step: "conclusion:goodbye_content" # Jump to end
|
|
|
|
# ==============================================================================
|
|
# METADATA SPECIAL VALUES
|
|
# ==============================================================================
|
|
# Reference guide for all metadata operations
|
|
|
|
# String Operations:
|
|
# ------------------
|
|
# "the-users-response" → Exact text of user's answer
|
|
# "n+,value" → Append to comma-separated list
|
|
# "n-,value" → Remove from comma-separated list
|
|
|
|
# Numeric Operations:
|
|
# -------------------
|
|
# "n+5" → Add 5 to existing value (or 0)
|
|
# "n-3" → Subtract 3 from existing value
|
|
# "n+random(1,10)" → Add random number between 1 and 10
|
|
|
|
# Static Values:
|
|
# --------------
|
|
# "any string" → Store literal string
|
|
# 42 → Store integer
|
|
# true / false → Store boolean
|
|
|
|
# ==============================================================================
|
|
# ADVANCED FEATURES (New in v2.0)
|
|
# ==============================================================================
|
|
|
|
# ==============================================================================
|
|
# TEMPLATE VARIABLES
|
|
# ==============================================================================
|
|
# Use {{variable_name}} syntax to insert dynamic values into content
|
|
|
|
# Available in: content_blocks, questions, ai_feedback tokens
|
|
|
|
# Built-in Variables:
|
|
# -------------------
|
|
# {{current_attempt}} → Current attempt number (1, 2, 3...)
|
|
# {{max_attempts}} → Maximum attempts allowed for this step
|
|
# {{attempts_remaining}} → How many attempts left (max - current)
|
|
# {{current_section}} → Current section_id
|
|
# {{current_step}} → Current step_id
|
|
# {{username}} → Name of the user who last responded
|
|
|
|
# Metadata Variables:
|
|
# -------------------
|
|
# {{metadata.key_name}} → Access any metadata value
|
|
# {{metadata.score}} → Example: access score
|
|
# {{metadata.player_name}} → Example: access player name
|
|
|
|
# Example Usage:
|
|
content_blocks:
|
|
- "## Your Progress"
|
|
- "Welcome back, {{metadata.player_name}}!"
|
|
- "Score: {{metadata.score}}"
|
|
- "Level: {{metadata.level}}"
|
|
- "Attempt {{current_attempt}} of {{max_attempts}}"
|
|
- "You have {{attempts_remaining}} tries remaining"
|
|
|
|
question: "{{metadata.character_name}} asks: What will you do?"
|
|
|
|
# Templates work in:
|
|
# - step content_blocks
|
|
# - transition content_blocks
|
|
# - question text
|
|
# - ai_feedback tokens_for_ai (for context, not rendered directly)
|
|
|
|
# ==============================================================================
|
|
# CONDITIONAL CONTENT BLOCKS
|
|
# ==============================================================================
|
|
# Show/hide content blocks based on metadata conditions
|
|
|
|
# Format: Each content block can be a string OR an object with conditions
|
|
|
|
content_blocks:
|
|
# Simple string - always shown
|
|
- "This is always displayed"
|
|
|
|
# Conditional block - only shown if conditions met
|
|
- text: "You're doing great! Keep going!"
|
|
show_if:
|
|
score_gte: 50 # Only show if score >= 50
|
|
|
|
- text: "Need more practice. Don't give up!"
|
|
show_if:
|
|
score_lt: 50 # Only show if score < 50
|
|
|
|
- text: "You found the secret key! 🗝️"
|
|
show_if:
|
|
inventory_contains: "key" # Only if inventory contains "key"
|
|
|
|
- text: "Welcome, warrior! ⚔️"
|
|
show_if:
|
|
class: "warrior" # Only if metadata.class equals "warrior"
|
|
|
|
- text: "Welcome, mage! 🔮"
|
|
show_if:
|
|
class: "mage"
|
|
|
|
# Conditional blocks reduce step duplication - one step, multiple paths!
|
|
|
|
# ==============================================================================
|
|
# ADVANCED METADATA CONDITIONS
|
|
# ==============================================================================
|
|
# Rich comparison operators for metadata_conditions
|
|
|
|
# Previously only supported equality:
|
|
metadata_conditions:
|
|
level: 5 # metadata.level must equal 5
|
|
|
|
# Now supports:
|
|
# --------------
|
|
|
|
# Equality & Inequality:
|
|
metadata_conditions:
|
|
status: "active" # Equal to "active"
|
|
status_ne: "inactive" # Not equal to "inactive"
|
|
|
|
# Numeric Comparisons:
|
|
metadata_conditions:
|
|
score_gte: 100 # Greater than or equal to 100
|
|
score_gt: 99 # Greater than 99
|
|
score_lt: 200 # Less than 200
|
|
score_lte: 199 # Less than or equal to 199
|
|
level_between: [5, 10] # Between 5 and 10 (inclusive)
|
|
|
|
# String Operations:
|
|
metadata_conditions:
|
|
inventory_contains: "sword" # Comma-separated list contains "sword"
|
|
inventory_not_contains: "poison" # List does NOT contain "poison"
|
|
name_matches: "^[A-Z]" # Regex match (starts with capital)
|
|
|
|
# Existence Checks:
|
|
metadata_conditions:
|
|
has_key_exists: true # Key "has_key" must exist in metadata
|
|
temp_flag_not_exists: true # Key "temp_flag" must NOT exist
|
|
|
|
# Boolean Checks:
|
|
metadata_conditions:
|
|
is_admin: true # metadata.is_admin must be true
|
|
is_locked: false # metadata.is_locked must be false
|
|
|
|
# Combining Multiple Conditions (ALL must be true):
|
|
metadata_conditions:
|
|
score_gte: 100
|
|
level_gte: 5
|
|
inventory_contains: "key"
|
|
quest_completed: true
|
|
# All four conditions must be met
|
|
|
|
# ==============================================================================
|
|
# CONDITIONAL NAVIGATION
|
|
# ==============================================================================
|
|
# Choose different paths based on metadata state
|
|
|
|
# OLD WAY (still works):
|
|
transitions:
|
|
answer_provided:
|
|
next_section_and_step: "section_2:step_1"
|
|
|
|
# NEW WAY - Conditional branches:
|
|
transitions:
|
|
answer_provided:
|
|
next_section_and_step:
|
|
- if:
|
|
score_gte: 100
|
|
goto: "expert:challenge"
|
|
|
|
- elif:
|
|
score_gte: 50
|
|
goto: "intermediate:lesson"
|
|
|
|
- elif:
|
|
score_gte: 25
|
|
goto: "beginner:practice"
|
|
|
|
- else:
|
|
goto: "tutorial:basics"
|
|
|
|
# Another example: Quest completion paths
|
|
transitions:
|
|
quest_complete:
|
|
next_section_and_step:
|
|
- if:
|
|
all_secrets_found: true
|
|
perfect_score: true
|
|
goto: "endings:perfect_ending"
|
|
|
|
- elif:
|
|
all_secrets_found: true
|
|
goto: "endings:good_ending"
|
|
|
|
- elif:
|
|
quest_failed: true
|
|
goto: "endings:bad_ending"
|
|
|
|
- else:
|
|
goto: "endings:neutral_ending"
|
|
|
|
# Conditions use same operators as metadata_conditions:
|
|
# - Equality: key: value
|
|
# - Comparisons: key_gte, key_gt, key_lt, key_lte
|
|
# - String ops: key_contains, key_not_contains, key_matches
|
|
# - Existence: key_exists, key_not_exists
|
|
# - Boolean: key: true/false
|
|
|
|
# ==============================================================================
|
|
# PROGRESSIVE HINTS SYSTEM
|
|
# ==============================================================================
|
|
# Built-in system for providing hints that escalate with attempts
|
|
#
|
|
# Example step with progressive hints:
|
|
#
|
|
# - step_id: "difficult_question"
|
|
# question: "What is the capital of Burkina Faso?"
|
|
#
|
|
# hints:
|
|
# - attempt: 1
|
|
# text: "💡 Hint: It's not the largest city in the country."
|
|
# counts_as_attempt: false
|
|
#
|
|
# - attempt: 2
|
|
# text: "💡 Hint: The name means 'City of Honest People'."
|
|
# counts_as_attempt: false
|
|
#
|
|
# - attempt: 3
|
|
# text: "💡 Strong Hint: It starts with 'Oua' and ends with 'dougou'."
|
|
# counts_as_attempt: false
|
|
#
|
|
# buckets: [correct, incorrect, need_hint]
|
|
#
|
|
# transitions:
|
|
# correct:
|
|
# content_blocks:
|
|
# - "Excellent! Ouagadougou is correct!"
|
|
# next_section_and_step: "next:step"
|
|
#
|
|
# incorrect:
|
|
# content_blocks:
|
|
# - "Not quite. Try again!"
|
|
# next_section_and_step: "current:difficult_question"
|
|
#
|
|
# need_hint:
|
|
# content_blocks:
|
|
# - "Let me help you..."
|
|
# counts_as_attempt: false
|
|
# next_section_and_step: "current:difficult_question"
|
|
#
|
|
# Hints auto-display when attempt number matches
|
|
# Hints support template variables: "Attempt {{current_attempt}} of {{max_attempts}}"
|
|
|
|
progressive_hints_example: "See activity-test-v2-features.yaml for working example"
|
|
|
|
# ==============================================================================
|
|
# WEIGHTED RANDOM SELECTION
|
|
# ==============================================================================
|
|
# Choose random values with different probabilities
|
|
|
|
# OLD WAY - Equal probability:
|
|
metadata_random:
|
|
loot: "sword" # 33% each
|
|
loot: "dagger" # 33% each
|
|
loot: "staff" # 33% each
|
|
|
|
# NEW WAY - Weighted probabilities:
|
|
metadata_weighted_random:
|
|
loot:
|
|
- value: "common_sword"
|
|
weight: 70 # 70% chance
|
|
- value: "rare_dagger"
|
|
weight: 25 # 25% chance
|
|
- value: "legendary_staff"
|
|
weight: 5 # 5% chance
|
|
|
|
# Weights don't need to sum to 100 - they're relative:
|
|
metadata_weighted_random:
|
|
reward:
|
|
- value: "gold"
|
|
weight: 10 # 10/(10+3+1) = 71.4%
|
|
- value: "gem"
|
|
weight: 3 # 3/(10+3+1) = 21.4%
|
|
- value: "artifact"
|
|
weight: 1 # 1/(10+3+1) = 7.1%
|
|
|
|
# Also works with metadata_tmp_weighted_random for temporary values
|
|
|
|
# Example: Random encounter
|
|
transitions:
|
|
explore_forest:
|
|
metadata_weighted_random:
|
|
encounter:
|
|
- value: "nothing"
|
|
weight: 50 # 50% - No encounter
|
|
- value: "merchant"
|
|
weight: 30 # 30% - Friendly merchant
|
|
- value: "goblin"
|
|
weight: 15 # 15% - Fight goblin
|
|
- value: "treasure"
|
|
weight: 5 # 5% - Find treasure!
|
|
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
Describe what happens based on metadata.encounter:
|
|
- nothing: Peaceful walk through forest
|
|
- merchant: Meet a traveling merchant
|
|
- goblin: Surprise goblin attack!
|
|
- treasure: Discover hidden treasure chest!
|
|
|
|
# ==============================================================================
|
|
# DYNAMIC QUESTION TEXT
|
|
# ==============================================================================
|
|
# Questions can now use template variables
|
|
|
|
# Static question (old way):
|
|
question: "What is 2 + 2?"
|
|
|
|
# Dynamic question with templates (new way):
|
|
question: "What is {{metadata.num1}} + {{metadata.num2}}?"
|
|
|
|
# Example: Math quiz with random numbers
|
|
#
|
|
# - step_id: "addition"
|
|
# pre_script: |
|
|
# import random
|
|
# result = {
|
|
# "metadata": {
|
|
# "num1": random.randint(1, 10),
|
|
# "num2": random.randint(1, 10)
|
|
# }
|
|
# }
|
|
# return result
|
|
#
|
|
# question: "What is {{metadata.num1}} + {{metadata.num2}}?"
|
|
#
|
|
# tokens_for_ai: |
|
|
# Calculate the correct answer: {{metadata.num1}} + {{metadata.num2}}
|
|
# Categorize as 'correct' if their answer matches.
|
|
#
|
|
# buckets: [correct, incorrect]
|
|
#
|
|
# More personalized question examples:
|
|
# - "{{metadata.character_name}}, what is your quest?"
|
|
# - "You have {{metadata.gold}} gold. How much do you spend?"
|
|
# - "Round {{current_attempt}}: What's your move?"
|
|
|
|
dynamic_question_example: "See activity-test-v2-features.yaml for working example"
|
|
|
|
# ==============================================================================
|
|
# BUILT-IN ATTEMPT COUNTER ACCESS
|
|
# ==============================================================================
|
|
# Access attempt information in templates
|
|
|
|
# Available variables:
|
|
# - {{current_attempt}} : 1, 2, 3, ... (current attempt number)
|
|
# - {{max_attempts}} : 3 (or custom value from default_max_attempts_per_step)
|
|
# - {{attempts_remaining}} : max_attempts - current_attempt
|
|
|
|
# Examples:
|
|
|
|
content_blocks:
|
|
- "Attempt {{current_attempt}} of {{max_attempts}}"
|
|
- "You have {{attempts_remaining}} tries left"
|
|
|
|
question: "Try {{current_attempt}}: What's your answer?"
|
|
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
This is attempt {{current_attempt}} of {{max_attempts}}.
|
|
They have {{attempts_remaining}} attempts remaining.
|
|
|
|
Adjust your feedback based on the attempt number:
|
|
- On their last attempt: Be clear and helpful
|
|
- With 2 attempts left: Provide a gentle hint
|
|
- With more attempts: Encourage them to think carefully
|
|
|
|
# Conditional content based on attempts:
|
|
content_blocks:
|
|
- text: "First try - think carefully!"
|
|
show_if:
|
|
current_attempt: 1
|
|
|
|
- text: "Second try - you're getting closer!"
|
|
show_if:
|
|
current_attempt: 2
|
|
|
|
- text: "Last chance! Here's a hint..."
|
|
show_if:
|
|
current_attempt: 3
|
|
|
|
# ==============================================================================
|
|
# SESSION PERSISTENCE (Twitch Plays Model)
|
|
# ==============================================================================
|
|
# How metadata and state persist across users and sessions
|
|
|
|
# Key Facts:
|
|
# ----------
|
|
# 1. ONE GAME STATE PER ROOM: All users in a room share the same activity state
|
|
# 2. METADATA IS SHARED: When one user updates metadata, all users see it
|
|
# 3. DATABASE PERSISTENCE: State survives browser refreshes and reconnections
|
|
# 4. ANYONE CAN CONTROL: Any user can provide input to advance the shared game
|
|
# 5. LIKE TWITCH PLAYS POKEMON: Collaborative control of single game instance
|
|
|
|
# Lifecycle:
|
|
# ----------
|
|
# Activity starts → State saved to database (room_id, section_id, step_id, metadata)
|
|
# User interacts → Metadata updates, state progresses
|
|
# Browser refreshes → State persists (loaded from database)
|
|
# Activity completes → State deleted from database
|
|
# Activity canceled → State deleted from database
|
|
|
|
# Use Cases:
|
|
# ----------
|
|
# - Classroom: Teacher projects, students call out answers collectively
|
|
# - Collaboration: Multiple people solve puzzle together
|
|
# - Public challenges: Community progresses through shared experience
|
|
# - Learning together: Everyone learns from same shared game state
|
|
|
|
# Implications for Activity Design:
|
|
# ----------------------------------
|
|
# - Design for SHARED state, not per-player state
|
|
# - Metadata represents THE GAME, not individual players
|
|
# - Multiple users may answer - first valid response advances
|
|
# - Consider: "What if 10 people are playing together?"
|
|
|
|
# ==============================================================================
|
|
# VALIDATION RULES
|
|
# ==============================================================================
|
|
|
|
# REQUIRED:
|
|
# ---------
|
|
# ✓ Every activity must have "sections" (at least one)
|
|
# ✓ Every section needs: section_id, title, steps
|
|
# ✓ Every step needs: step_id, title
|
|
# ✓ Every step needs EITHER content_blocks OR question (or both)
|
|
# ✓ Steps with questions need: buckets, transitions, tokens_for_ai
|
|
# ✓ Every bucket must have a corresponding transition
|
|
# ✓ All next_section_and_step targets must exist
|
|
|
|
# FORBIDDEN:
|
|
# ----------
|
|
# ✗ Terminal steps (no next_section_and_step) CANNOT have questions
|
|
# ✗ Section IDs must be unique within activity
|
|
# ✗ Step IDs must be unique within section
|
|
# ✗ Random bucket names must exist in main buckets list
|
|
# ✗ Random bucket probabilities must be 0.0 to 1.0
|
|
|
|
# WARNINGS:
|
|
# ---------
|
|
# ⚠ Total random bucket probability > 1.0 (overlapping events)
|
|
# ⚠ Circular loops without exit path
|
|
# ⚠ Python syntax errors in processing scripts
|
|
|
|
# ==============================================================================
|
|
# BEST PRACTICES
|
|
# ==============================================================================
|
|
|
|
# 1. START SIMPLE
|
|
# - Begin with content-only steps and simple questions
|
|
# - Add complexity incrementally
|
|
# - Test frequently with CLI simulator
|
|
|
|
# 2. CLEAR INSTRUCTIONS
|
|
# - Write specific tokens_for_ai that explain each bucket clearly
|
|
# - Give examples of what qualifies for each category
|
|
# - Be generous in accepting valid responses
|
|
|
|
# 3. METADATA STRATEGY
|
|
# - Track meaningful state: score, progress, user choices
|
|
# - Use descriptive key names: "programming_language" not "pl"
|
|
# - Clean up temporary metadata with metadata_tmp_add
|
|
|
|
# 4. RANDOM EVENTS
|
|
# - Use probabilities that feel right (5-15% for rare events)
|
|
# - Set counts_as_attempt: false for random buckets
|
|
# - Don't override user navigation unless necessary
|
|
|
|
# 5. FEEDBACK QUALITY
|
|
# - Reference specific parts of user's answer
|
|
# - Provide actionable suggestions for improvement
|
|
# - Celebrate progress and effort
|
|
|
|
# 6. TERMINATION
|
|
# - Always provide clear path to completion
|
|
# - Mark completion: metadata_add: activity_completed: "true"
|
|
# - Give users a sense of accomplishment
|
|
|
|
# 7. TESTING
|
|
# - Validate YAML: python activity_yaml_validator.py your_activity.yaml
|
|
# - Test all paths: source vars.sh && python research/guarded_ai.py your_activity.yaml
|
|
# - Try wrong answers, edge cases, language switching
|
|
|
|
# ==============================================================================
|
|
# MODEL CONFIGURATION
|
|
# ==============================================================================
|
|
|
|
# Environment Variables (in vars.sh):
|
|
# ------------------------------------
|
|
# MODEL_ENDPOINT_1=http://localhost:8080/v1
|
|
# MODEL_API_KEY_1=your-api-key
|
|
# MODEL_NAME_1=model # Optional: actual model name for endpoint
|
|
#
|
|
# MODEL_ENDPOINT_2=http://localhost:8081/v1
|
|
# MODEL_API_KEY_2=your-api-key
|
|
# MODEL_NAME_2=gpt-4
|
|
#
|
|
# MODEL_ENDPOINT_3=http://localhost:8082/v1
|
|
# MODEL_API_KEY_3=your-api-key
|
|
# MODEL_NAME_3=model
|
|
|
|
# Recommended Models:
|
|
# -------------------
|
|
# MODEL_1: Hermes-3-Llama-3.1-8B (default, fast, excellent for classification)
|
|
# MODEL_2: Larger general model (if available)
|
|
# MODEL_3: Qwen3-Coder-30B (for programming activities)
|
|
|
|
# Model Selection Strategy:
|
|
# -------------------------
|
|
# - Classifier: Use MODEL_1 (fast 8B model) for instant categorization
|
|
# - Feedback: Use specialized model for domain-specific feedback
|
|
# - Programming → MODEL_3 (Qwen3-Coder)
|
|
# - General → MODEL_1 (Hermes)
|
|
# - Advanced reasoning → MODEL_2 (larger model)
|
|
|
|
# ==============================================================================
|
|
# EXAMPLES
|
|
# ==============================================================================
|
|
|
|
# See these reference activities:
|
|
# -------------------------------
|
|
# activity26-magic-8-ball.yaml - Looping, randomness, replayability
|
|
# activity31-scientific-method.yaml - Educational scaffolding
|
|
# activity37-programming-languages.yaml - Model overrides, code generation
|
|
# activity40-fashion-empire-backrooms.yaml - Random buckets, complex navigation
|
|
|
|
# ==============================================================================
|
|
# END OF SPECIFICATION
|
|
# ==============================================================================
|