opencompletion.com/research/SPEC.yaml
Claude bd779e06fa
Fix SPEC.yaml validation and refactor guarded_ai.py for v2.0 consistency
SPEC.yaml fixes:
- Comment out orphaned example code blocks that broke YAML parsing
- Convert progressive hints and dynamic question examples to comments
- Add placeholder keys to maintain valid YAML structure
- All examples now documented but non-executable (reference only)
- Validates with 0 errors

guarded_ai.py refactor (CLI simulator now uses v2.0 features):
- Import activity_utils.py for consistency with activity.py
- Use check_conditions() for advanced metadata conditions (gte, lt, contains, etc.)
- Use filter_content_blocks() for template rendering and conditional blocks
- Use render_template() for dynamic question text with {{variables}}
- Use resolve_conditional_navigation() for if/elif/else navigation
- Use select_weighted_random() for weighted random selection
- Use get_progressive_hint() for progressive hints system
- Create template contexts with built-in variables (current_attempt, etc.)

Benefits:
- Single source of truth for v2.0 logic (activity_utils.py)
- CLI simulator now tests all v2.0 features
- Maintainability: changes to features only need updates in one place
- Consistency: web app and CLI behave identically

All changes validated and tested.
2025-11-10 15:19:50 +00:00

1148 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}}.
{% if attempts_remaining == 1 %}
This is their last chance! Be clear and helpful.
{% elif attempts_remaining == 2 %}
They still have time. Provide a gentle hint.
{% else %}
Encourage them to think carefully.
{% endif %}
# 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
# ==============================================================================