clawed 2.3.2__tar.gz → 2.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. {clawed-2.3.2 → clawed-2.3.3}/PKG-INFO +1 -1
  2. {clawed-2.3.2 → clawed-2.3.3}/clawed/__init__.py +1 -1
  3. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/generate_lesson_bundle.py +65 -10
  4. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/update_soul.py +11 -2
  5. {clawed-2.3.2 → clawed-2.3.3}/clawed/llm.py +26 -0
  6. {clawed-2.3.2 → clawed-2.3.3}/clawed/memory_engine.py +29 -0
  7. {clawed-2.3.2 → clawed-2.3.3}/clawed/models.py +9 -0
  8. clawed-2.3.3/clawed/persona_evolution.py +271 -0
  9. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/admin_lesson_plan.txt +5 -1
  10. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/lesson_plan.txt +5 -1
  11. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/persona_extract.txt +11 -1
  12. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/student_packet.txt +10 -0
  13. {clawed-2.3.2 → clawed-2.3.3}/clawed/reading_report.py +186 -2
  14. {clawed-2.3.2 → clawed-2.3.3}/clawed/slide_images.py +29 -3
  15. clawed-2.3.3/clawed/voice_check.py +192 -0
  16. {clawed-2.3.2 → clawed-2.3.3}/clawed/workspace.py +110 -0
  17. {clawed-2.3.2 → clawed-2.3.3}/pyproject.toml +1 -1
  18. {clawed-2.3.2 → clawed-2.3.3}/.gitignore +0 -0
  19. {clawed-2.3.2 → clawed-2.3.3}/LICENSE +0 -0
  20. {clawed-2.3.2 → clawed-2.3.3}/README.md +0 -0
  21. {clawed-2.3.2 → clawed-2.3.3}/clawed/__main__.py +0 -0
  22. {clawed-2.3.2 → clawed-2.3.3}/clawed/_legacy_gateway.py +0 -0
  23. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent.py +0 -0
  24. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/__init__.py +0 -0
  25. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/approvals.py +0 -0
  26. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/autonomy.py +0 -0
  27. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/context.py +0 -0
  28. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/core.py +0 -0
  29. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/custom_tools.py +0 -0
  30. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/drive/__init__.py +0 -0
  31. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/drive/auth.py +0 -0
  32. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/drive/client.py +0 -0
  33. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/fake_llm.py +0 -0
  34. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/loop.py +0 -0
  35. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/__init__.py +0 -0
  36. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/curriculum.py +0 -0
  37. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/curriculum_kb.py +0 -0
  38. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/embeddings.py +0 -0
  39. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/episodes.py +0 -0
  40. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/identity.py +0 -0
  41. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/loader.py +0 -0
  42. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/memory/preferences.py +0 -0
  43. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/planner.py +0 -0
  44. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/prompt.py +0 -0
  45. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/scheduler.py +0 -0
  46. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/__init__.py +0 -0
  47. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/base.py +0 -0
  48. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/configure_profile.py +0 -0
  49. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/curriculum_map.py +0 -0
  50. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_create_doc.py +0 -0
  51. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_create_slides.py +0 -0
  52. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_list.py +0 -0
  53. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_organize.py +0 -0
  54. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_read.py +0 -0
  55. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/drive_upload.py +0 -0
  56. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/export_document.py +0 -0
  57. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/gap_analysis.py +0 -0
  58. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/generate_assessment.py +0 -0
  59. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/generate_lesson.py +0 -0
  60. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/generate_materials.py +0 -0
  61. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/generate_unit.py +0 -0
  62. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/ingest_materials.py +0 -0
  63. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/parent_comm.py +0 -0
  64. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/read_heartbeat.py +0 -0
  65. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/read_workspace.py +0 -0
  66. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/request_approval.py +0 -0
  67. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/schedule_task.py +0 -0
  68. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/search_lessons.py +0 -0
  69. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/search_my_materials.py +0 -0
  70. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/search_standards.py +0 -0
  71. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/student_insights.py +0 -0
  72. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/sub_packet.py +0 -0
  73. {clawed-2.3.2 → clawed-2.3.3}/clawed/agent_core/tools/switch_model.py +0 -0
  74. {clawed-2.3.2 → clawed-2.3.3}/clawed/analytics.py +0 -0
  75. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/__init__.py +0 -0
  76. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/deps.py +0 -0
  77. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/__init__.py +0 -0
  78. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/chat.py +0 -0
  79. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/export.py +0 -0
  80. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/feedback.py +0 -0
  81. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/gateway_chat.py +0 -0
  82. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/generate.py +0 -0
  83. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/ingest.py +0 -0
  84. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/lessons.py +0 -0
  85. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/school.py +0 -0
  86. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/settings.py +0 -0
  87. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/routes/tools.py +0 -0
  88. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/server.py +0 -0
  89. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/static/app.js +0 -0
  90. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/static/style.css +0 -0
  91. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/static/widget.js +0 -0
  92. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/analytics.html +0 -0
  93. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/base.html +0 -0
  94. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/dashboard.html +0 -0
  95. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/generate.html +0 -0
  96. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/index.html +0 -0
  97. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/lesson.html +0 -0
  98. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/profile.html +0 -0
  99. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/settings.html +0 -0
  100. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/stats.html +0 -0
  101. {clawed-2.3.2 → clawed-2.3.3}/clawed/api/templates/students.html +0 -0
  102. {clawed-2.3.2 → clawed-2.3.3}/clawed/assessment.py +0 -0
  103. {clawed-2.3.2 → clawed-2.3.3}/clawed/asset_registry.py +0 -0
  104. {clawed-2.3.2 → clawed-2.3.3}/clawed/auth/__init__.py +0 -0
  105. {clawed-2.3.2 → clawed-2.3.3}/clawed/auth/google_auth.py +0 -0
  106. {clawed-2.3.2 → clawed-2.3.3}/clawed/bot_state.py +0 -0
  107. {clawed-2.3.2 → clawed-2.3.3}/clawed/chat.py +0 -0
  108. {clawed-2.3.2 → clawed-2.3.3}/clawed/cli.py +0 -0
  109. {clawed-2.3.2 → clawed-2.3.3}/clawed/cli_chat.py +0 -0
  110. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/__init__.py +0 -0
  111. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/_helpers.py +0 -0
  112. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/bot.py +0 -0
  113. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/config.py +0 -0
  114. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/config_llm.py +0 -0
  115. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/config_profile.py +0 -0
  116. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/export.py +0 -0
  117. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/generate.py +0 -0
  118. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/generate_assessment.py +0 -0
  119. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/generate_unit.py +0 -0
  120. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/queue.py +0 -0
  121. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/schedule_cmd.py +0 -0
  122. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/sub.py +0 -0
  123. {clawed-2.3.2 → clawed-2.3.3}/clawed/commands/workspace_cmd.py +0 -0
  124. {clawed-2.3.2 → clawed-2.3.3}/clawed/config.py +0 -0
  125. {clawed-2.3.2 → clawed-2.3.3}/clawed/corpus.py +0 -0
  126. {clawed-2.3.2 → clawed-2.3.3}/clawed/curriculum_map.py +0 -0
  127. {clawed-2.3.2 → clawed-2.3.3}/clawed/database.py +0 -0
  128. {clawed-2.3.2 → clawed-2.3.3}/clawed/demo/__init__.py +0 -0
  129. {clawed-2.3.2 → clawed-2.3.3}/clawed/demo/demo_assessment.json +0 -0
  130. {clawed-2.3.2 → clawed-2.3.3}/clawed/demo/demo_lesson_science_g6.json +0 -0
  131. {clawed-2.3.2 → clawed-2.3.3}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
  132. {clawed-2.3.2 → clawed-2.3.3}/clawed/demo/demo_unit_plan.json +0 -0
  133. {clawed-2.3.2 → clawed-2.3.3}/clawed/differentiation.py +0 -0
  134. {clawed-2.3.2 → clawed-2.3.3}/clawed/doc_export.py +0 -0
  135. {clawed-2.3.2 → clawed-2.3.3}/clawed/drive.py +0 -0
  136. {clawed-2.3.2 → clawed-2.3.3}/clawed/evaluation.py +0 -0
  137. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_docx.py +0 -0
  138. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_handout.py +0 -0
  139. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_markdown.py +0 -0
  140. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_pdf.py +0 -0
  141. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_pptx.py +0 -0
  142. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_templates.py +0 -0
  143. {clawed-2.3.2 → clawed-2.3.3}/clawed/export_theme.py +0 -0
  144. {clawed-2.3.2 → clawed-2.3.3}/clawed/exporter.py +0 -0
  145. {clawed-2.3.2 → clawed-2.3.3}/clawed/feedback.py +0 -0
  146. {clawed-2.3.2 → clawed-2.3.3}/clawed/formats/__init__.py +0 -0
  147. {clawed-2.3.2 → clawed-2.3.3}/clawed/formats/flipchart.py +0 -0
  148. {clawed-2.3.2 → clawed-2.3.3}/clawed/formats/notebook.py +0 -0
  149. {clawed-2.3.2 → clawed-2.3.3}/clawed/formats/xbk.py +0 -0
  150. {clawed-2.3.2 → clawed-2.3.3}/clawed/gateway.py +0 -0
  151. {clawed-2.3.2 → clawed-2.3.3}/clawed/gateway_response.py +0 -0
  152. {clawed-2.3.2 → clawed-2.3.3}/clawed/generation.py +0 -0
  153. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/__init__.py +0 -0
  154. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/export.py +0 -0
  155. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/feedback.py +0 -0
  156. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/gaps.py +0 -0
  157. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/generate.py +0 -0
  158. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/ingest.py +0 -0
  159. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/misc.py +0 -0
  160. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/onboard.py +0 -0
  161. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/schedule.py +0 -0
  162. {clawed-2.3.2 → clawed-2.3.3}/clawed/handlers/standards.py +0 -0
  163. {clawed-2.3.2 → clawed-2.3.3}/clawed/improver.py +0 -0
  164. {clawed-2.3.2 → clawed-2.3.3}/clawed/ingestor.py +0 -0
  165. {clawed-2.3.2 → clawed-2.3.3}/clawed/io.py +0 -0
  166. {clawed-2.3.2 → clawed-2.3.3}/clawed/lesson.py +0 -0
  167. {clawed-2.3.2 → clawed-2.3.3}/clawed/materials.py +0 -0
  168. {clawed-2.3.2 → clawed-2.3.3}/clawed/mcp_server.py +0 -0
  169. {clawed-2.3.2 → clawed-2.3.3}/clawed/model_router.py +0 -0
  170. {clawed-2.3.2 → clawed-2.3.3}/clawed/onboarding.py +0 -0
  171. {clawed-2.3.2 → clawed-2.3.3}/clawed/openclaw_plugin.py +0 -0
  172. {clawed-2.3.2 → clawed-2.3.3}/clawed/parent_comm.py +0 -0
  173. {clawed-2.3.2 → clawed-2.3.3}/clawed/persona.py +0 -0
  174. {clawed-2.3.2 → clawed-2.3.3}/clawed/planner.py +0 -0
  175. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/504_accommodations.txt +0 -0
  176. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/assessment.txt +0 -0
  177. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/curriculum_gaps.txt +0 -0
  178. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/dbq_assessment.txt +0 -0
  179. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/differentiation.txt +0 -0
  180. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/formative_assessment.txt +0 -0
  181. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/iep_modification.txt +0 -0
  182. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/pacing_guide.txt +0 -0
  183. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/parent_note.txt +0 -0
  184. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/quiz.txt +0 -0
  185. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/rubric.txt +0 -0
  186. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/sub_packet.txt +0 -0
  187. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/summative_assessment.txt +0 -0
  188. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/tiered_assignments.txt +0 -0
  189. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/unit_plan.txt +0 -0
  190. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/worksheet.txt +0 -0
  191. {clawed-2.3.2 → clawed-2.3.3}/clawed/prompts/year_map.txt +0 -0
  192. {clawed-2.3.2 → clawed-2.3.3}/clawed/quality.py +0 -0
  193. {clawed-2.3.2 → clawed-2.3.3}/clawed/router.py +0 -0
  194. {clawed-2.3.2 → clawed-2.3.3}/clawed/sanitize.py +0 -0
  195. {clawed-2.3.2 → clawed-2.3.3}/clawed/scheduler.py +0 -0
  196. {clawed-2.3.2 → clawed-2.3.3}/clawed/school.py +0 -0
  197. {clawed-2.3.2 → clawed-2.3.3}/clawed/search.py +0 -0
  198. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/__init__.py +0 -0
  199. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/art.py +0 -0
  200. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/base.py +0 -0
  201. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/computer_science.py +0 -0
  202. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/ela.py +0 -0
  203. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/foreign_language.py +0 -0
  204. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/history.py +0 -0
  205. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/library.py +0 -0
  206. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/math.py +0 -0
  207. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/music.py +0 -0
  208. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/physical_education.py +0 -0
  209. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/science.py +0 -0
  210. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/social_studies.py +0 -0
  211. {clawed-2.3.2 → clawed-2.3.3}/clawed/skills/special_education.py +0 -0
  212. {clawed-2.3.2 → clawed-2.3.3}/clawed/standards.py +0 -0
  213. {clawed-2.3.2 → clawed-2.3.3}/clawed/state.py +0 -0
  214. {clawed-2.3.2 → clawed-2.3.3}/clawed/state_standards.py +0 -0
  215. {clawed-2.3.2 → clawed-2.3.3}/clawed/student_bot.py +0 -0
  216. {clawed-2.3.2 → clawed-2.3.3}/clawed/student_cli.py +0 -0
  217. {clawed-2.3.2 → clawed-2.3.3}/clawed/student_telegram_bot.py +0 -0
  218. {clawed-2.3.2 → clawed-2.3.3}/clawed/sub_packet.py +0 -0
  219. {clawed-2.3.2 → clawed-2.3.3}/clawed/task_queue.py +0 -0
  220. {clawed-2.3.2 → clawed-2.3.3}/clawed/templates_lib.py +0 -0
  221. {clawed-2.3.2 → clawed-2.3.3}/clawed/tools.py +0 -0
  222. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/__init__.py +0 -0
  223. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/cli.py +0 -0
  224. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/openclaw.py +0 -0
  225. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/student_telegram.py +0 -0
  226. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/telegram.py +0 -0
  227. {clawed-2.3.2 → clawed-2.3.3}/clawed/transports/web.py +0 -0
  228. {clawed-2.3.2 → clawed-2.3.3}/clawed/tui.py +0 -0
  229. {clawed-2.3.2 → clawed-2.3.3}/clawed/tui_chat.py +0 -0
  230. {clawed-2.3.2 → clawed-2.3.3}/clawed/voice.py +0 -0
  231. {clawed-2.3.2 → clawed-2.3.3}/eduagent/__init__.py +0 -0
  232. {clawed-2.3.2 → clawed-2.3.3}/eduagent/_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawed
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: Claw-ED — personal AI teaching agent. Learns your voice, works while you sleep.
5
5
  Project-URL: Homepage, https://github.com/SirhanMacx/Claw-ED
6
6
  Project-URL: Documentation, https://github.com/SirhanMacx/Claw-ED#readme
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
17
17
  except Exception:
18
18
  pass
19
19
 
20
- __version__ = "2.3.2"
20
+ __version__ = "2.3.3"
21
21
  __author__ = "Jon Maccarello & Claw-ED contributors"
22
22
  __description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
23
23
 
@@ -232,6 +232,23 @@ class GenerateLessonBundleTool:
232
232
  except Exception:
233
233
  pass # Review is best-effort, don't block on failure
234
234
 
235
+ # ── Voice validation ──────────────────────────────────────────
236
+ voice_notes: list[str] = []
237
+ try:
238
+ from clawed.voice_check import check_voice_match
239
+
240
+ voice_result = check_voice_match(
241
+ persona=persona,
242
+ do_now=lesson.do_now,
243
+ direct_instruction_opening=lesson.direct_instruction[:500] if lesson.direct_instruction else "",
244
+ )
245
+ if not voice_result.passed:
246
+ for issue in voice_result.issues:
247
+ voice_notes.append(issue)
248
+ logger.info("Voice check issue: %s", issue)
249
+ except Exception as e:
250
+ logger.debug("Voice check failed: %s", e)
251
+
235
252
  # ── Generate student packet + admin plan in parallel ──────────
236
253
  import asyncio
237
254
 
@@ -323,19 +340,57 @@ class GenerateLessonBundleTool:
323
340
  logger.error("PPTX export failed: %s", e)
324
341
  errors.append(f"Slideshow PPTX failed: {e}")
325
342
 
326
- # ── Build response ───────────────────────────────────────────
327
- lines = [f"Generated teaching package for: {lesson.title}\n"]
328
- if generated_files:
329
- lines.append("Files created:")
330
- for f in generated_files:
331
- lines.append(f" - {f}")
332
- if kb_context:
333
- lines.append("\nReferenced your existing materials on this topic.")
334
- if errors:
335
- lines.append("\nErrors:")
343
+ # ── Build honest response ─────────────────────────────────────
344
+ used_fallback_packet = not student_packet and any("Student packet" in s for s in side_effects)
345
+
346
+ lines = []
347
+
348
+ if len(generated_files) == 3 and not errors:
349
+ lines.append(f"Complete teaching package for: {lesson.title}")
350
+ lines.append("All three files ready to print:")
351
+ for se in side_effects:
352
+ lines.append(f" - {se}")
353
+ elif generated_files:
354
+ lines.append(f"Generated {len(generated_files)} of 3 files for: {lesson.title}")
355
+ for se in side_effects:
356
+ lines.append(f" - {se}")
357
+ if errors:
358
+ lines.append("")
359
+ for err in errors:
360
+ clean_err = str(err).split("\n")[0][:200]
361
+ lines.append(f" Could not generate: {clean_err}")
362
+ lines.append("Want me to try the failed item(s) again?")
363
+ else:
364
+ lines.append(f"Failed to generate teaching package for: {lesson.title}")
336
365
  for err in errors:
337
366
  lines.append(f" - {err}")
338
367
 
368
+ if used_fallback_packet:
369
+ lines.append("")
370
+ lines.append(
371
+ "Note: The student packet was generated using a simpler method — "
372
+ "it may not have full graphic organizers. Let me know if you'd like me to regenerate it."
373
+ )
374
+
375
+ if kb_context:
376
+ lines.append("\nReferenced your existing materials on this topic.")
377
+
378
+ # Self-review findings
379
+ try:
380
+ if review and not review.get("passed", True) and review.get("issues"):
381
+ lines.append("")
382
+ lines.append("Quality notes:")
383
+ for issue in review["issues"][:3]:
384
+ lines.append(f" - {issue}")
385
+ except NameError:
386
+ pass
387
+
388
+ if voice_notes:
389
+ lines.append("\nVoice match notes:")
390
+ for note in voice_notes:
391
+ lines.append(f" - {note}")
392
+ lines.append("Want me to adjust the lesson to better match your voice?")
393
+
339
394
  return ToolResult(
340
395
  text="\n".join(lines),
341
396
  files=generated_files,
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
 
4
4
  import logging
5
5
  from datetime import date
6
- from pathlib import Path
7
6
  from typing import Any
8
7
 
9
8
  from clawed.agent_core.context import AgentContext, ToolResult
@@ -92,7 +91,9 @@ class UpdateSoulTool:
92
91
  text=f"Unknown section '{section_key}'. Valid sections: {valid}"
93
92
  )
94
93
 
95
- soul_path = Path.home() / ".eduagent" / "workspace" / "SOUL.md"
94
+ from clawed.workspace import SOUL_PATH
95
+
96
+ soul_path = SOUL_PATH
96
97
  soul_path.parent.mkdir(parents=True, exist_ok=True)
97
98
 
98
99
  # Read or create SOUL.md
@@ -104,6 +105,14 @@ class UpdateSoulTool:
104
105
  # Build the datestamped entry
105
106
  entry = f"\n\n*({date.today().isoformat()})* {content}\n"
106
107
 
108
+ # Check for duplicate before writing
109
+ from clawed.workspace import _deduplicate_entry
110
+
111
+ if _deduplicate_entry(current, content, header):
112
+ return ToolResult(
113
+ text=f"Observation already exists in SOUL.md section '{section_key}' — skipping duplicate.",
114
+ )
115
+
107
116
  # Insert after the section header
108
117
  if header in current:
109
118
  current = current.replace(header, header + entry, 1)
@@ -293,10 +293,36 @@ class LLMClient:
293
293
 
294
294
  prompt_path = Path(__file__).parent / "prompts" / "student_packet.txt"
295
295
  prompt_template = prompt_path.read_text(encoding="utf-8")
296
+
297
+ # Build handout style block from persona context
298
+ import re as _re
299
+ handout_style = ""
300
+ if persona_context:
301
+ hs_match = _re.search(
302
+ r"=== Handout Style ===\n(.+?)(?:\n===|\Z)",
303
+ persona_context, _re.DOTALL,
304
+ )
305
+ if hs_match:
306
+ handout_style = hs_match.group(1).strip()
307
+
308
+ if handout_style:
309
+ handout_style_block = (
310
+ f"This teacher's handout style: {handout_style}. "
311
+ "Match this format. If the teacher uses guided notes, include them. "
312
+ "If they use graphic organizers, lead with those. If they prefer "
313
+ "dense source packets, make the sources the centerpiece. "
314
+ "Don't impose a format the teacher wouldn't recognize as their own."
315
+ )
316
+ else:
317
+ handout_style_block = (
318
+ "No specific handout style detected — use the default format below."
319
+ )
320
+
296
321
  prompt = (
297
322
  prompt_template
298
323
  .replace("{lesson_json}", lesson_json[:6000])
299
324
  .replace("{persona}", persona_context)
325
+ .replace("{handout_style_block}", handout_style_block)
300
326
  )
301
327
 
302
328
  system = (
@@ -397,6 +397,35 @@ def process_feedback(
397
397
  except Exception as e:
398
398
  logger.debug("Drift detection failed: %s", e)
399
399
 
400
+ # Check if persona evolution should trigger
401
+ try:
402
+ from clawed.persona_evolution import apply_confirmed_changes, get_confirmed_changes
403
+ confirmed = get_confirmed_changes()
404
+ if confirmed:
405
+ from clawed.commands._helpers import persona_path
406
+ from clawed.persona import load_persona
407
+ pp = persona_path()
408
+ if pp.exists():
409
+ current_persona = load_persona(pp)
410
+ updated, descriptions = apply_confirmed_changes(current_persona)
411
+ if descriptions:
412
+ pp.write_text(updated.model_dump_json(indent=2), encoding="utf-8")
413
+ # Log to SOUL.md
414
+ from clawed.workspace import SOUL_PATH
415
+ if SOUL_PATH.exists():
416
+ soul_content = SOUL_PATH.read_text(encoding="utf-8")
417
+ stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d")
418
+ for desc in descriptions:
419
+ entry = f"\n\n*({stamp})* Fingerprint updated: {desc}\n"
420
+ marker = "## Agent Observations"
421
+ soul_content = soul_content.replace(
422
+ marker, marker + entry, 1
423
+ )
424
+ SOUL_PATH.write_text(soul_content, encoding="utf-8")
425
+ logger.info("Persona evolution applied: %s", "; ".join(descriptions))
426
+ except Exception as e:
427
+ logger.debug("Persona evolution check failed: %s", e)
428
+
400
429
  return applied
401
430
 
402
431
 
@@ -201,6 +201,11 @@ class TeacherPersona(BaseModel):
201
201
  'Always ends DI with "here is where it gets interesting"',
202
202
  'Uses physical movement — students walk to corners for opinion spectrums'."""
203
203
 
204
+ handout_style: str = ""
205
+ """Description of the teacher's handout/worksheet style, e.g.
206
+ 'Dense text packets with primary source excerpts and marginal annotations'
207
+ or 'Graphic organizer-heavy with minimal text, always includes an image hook'."""
208
+
204
209
  def to_prompt_context(self) -> str:
205
210
  """Serialize persona into a string for LLM prompt injection."""
206
211
  lines = [
@@ -250,6 +255,10 @@ class TeacherPersona(BaseModel):
250
255
  for move in self.signature_moves:
251
256
  lines.append(f"- {move}")
252
257
 
258
+ if self.handout_style:
259
+ lines.append(f"\n=== Handout Style ===\n{self.handout_style}")
260
+ lines.append("Student packets must match this format.")
261
+
253
262
  if self.voice_examples:
254
263
  lines.append("")
255
264
  lines.append("=== Voice Examples (write like this) ===")
@@ -0,0 +1,271 @@
1
+ """Pedagogical Fingerprint Evolution — conservative persona drift tracking.
2
+
3
+ Tracks changes to a teacher's persona over time. Changes require 2+
4
+ consistent signals (e.g. two separate ingestion runs that both detect
5
+ the same style shift) before they are applied. This prevents a single
6
+ noisy extraction from overwriting the teacher's carefully-built identity.
7
+
8
+ Flow:
9
+ 1. New files ingested → ``record_ingestion_changes()`` compares old/new
10
+ 2. Candidates accumulate in ``~/.eduagent/persona_candidates.json``
11
+ 3. Each feedback cycle calls ``get_confirmed_changes()``
12
+ 4. Only candidates with confirmations >= 2 are applied
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import os
20
+ from datetime import datetime, timezone
21
+ from enum import Enum
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from clawed.models import TeacherPersona
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # ── Evolvable fields ────────────────────────────────────────────────
30
+
31
+ _EVOLVABLE_FIELDS: list[str] = [
32
+ "teaching_style",
33
+ "do_now_style",
34
+ "exit_ticket_style",
35
+ "source_types",
36
+ "activity_patterns",
37
+ "scaffolding_moves",
38
+ "signature_moves",
39
+ "handout_style",
40
+ ]
41
+
42
+ # ── Confirmation threshold ──────────────────────────────────────────
43
+
44
+ _CONFIRMATION_THRESHOLD = 2
45
+
46
+ # ── Candidate file path ────────────────────────────────────────────
47
+
48
+ def _candidates_path() -> Path:
49
+ base = Path(os.environ.get("EDUAGENT_DATA_DIR", str(Path.home() / ".eduagent")))
50
+ return base / "persona_candidates.json"
51
+
52
+
53
+ def _load_candidates() -> list[dict[str, Any]]:
54
+ path = _candidates_path()
55
+ if not path.exists():
56
+ return []
57
+ try:
58
+ return json.loads(path.read_text(encoding="utf-8"))
59
+ except (json.JSONDecodeError, OSError):
60
+ logger.debug("Could not read persona candidates file; starting fresh")
61
+ return []
62
+
63
+
64
+ def _save_candidates(candidates: list[dict[str, Any]]) -> None:
65
+ path = _candidates_path()
66
+ path.parent.mkdir(parents=True, exist_ok=True)
67
+ path.write_text(json.dumps(candidates, indent=2, default=str), encoding="utf-8")
68
+
69
+
70
+ # ── Serialization helper ───────────────────────────────────────────
71
+
72
+ def _serialize(value: Any) -> Any:
73
+ """Convert a persona field value into a JSON-safe representation."""
74
+ if isinstance(value, Enum):
75
+ return value.value
76
+ if isinstance(value, list):
77
+ return [_serialize(v) for v in value]
78
+ return value
79
+
80
+
81
+ # ── Core comparison logic ──────────────────────────────────────────
82
+
83
+ def _compare_personas(
84
+ old: TeacherPersona,
85
+ new: TeacherPersona,
86
+ ) -> list[dict[str, Any]]:
87
+ """Compare two personas field-by-field across evolvable fields.
88
+
89
+ Returns a list of ``{"field": ..., "old_value": ..., "new_value": ...}``
90
+ dicts — one per changed field.
91
+ """
92
+ changes: list[dict[str, Any]] = []
93
+ for field in _EVOLVABLE_FIELDS:
94
+ old_val = _serialize(getattr(old, field, None))
95
+ new_val = _serialize(getattr(new, field, None))
96
+ if old_val != new_val:
97
+ changes.append({
98
+ "field": field,
99
+ "old_value": old_val,
100
+ "new_value": new_val,
101
+ })
102
+ return changes
103
+
104
+
105
+ def _build_candidate_changes(
106
+ old: TeacherPersona,
107
+ new: TeacherPersona,
108
+ source: str = "ingestion",
109
+ ) -> list[dict[str, Any]]:
110
+ """Wrap comparison results into timestamped candidate entries."""
111
+ raw_changes = _compare_personas(old, new)
112
+ now_iso = datetime.now(timezone.utc).isoformat()
113
+ candidates: list[dict[str, Any]] = []
114
+ for change in raw_changes:
115
+ candidates.append({
116
+ "field": change["field"],
117
+ "old_value": change["old_value"],
118
+ "new_value": change["new_value"],
119
+ "source": source,
120
+ "first_seen": now_iso,
121
+ "last_seen": now_iso,
122
+ "confirmations": 1,
123
+ })
124
+ return candidates
125
+
126
+
127
+ # ── Rating pattern analysis ────────────────────────────────────────
128
+
129
+ def _analyze_rating_patterns(
130
+ ratings: list[tuple[int, str]],
131
+ ) -> list[dict[str, Any]]:
132
+ """Analyze ``(rating, notes)`` tuples for style-shift signals.
133
+
134
+ Requires a minimum of 10 ratings to produce any output — small
135
+ sample sizes are too noisy to act on.
136
+ """
137
+ if len(ratings) < 10:
138
+ return []
139
+
140
+ signals: list[dict[str, Any]] = []
141
+
142
+ # Split into halves to detect trend shifts
143
+ mid = len(ratings) // 2
144
+ first_half = ratings[:mid]
145
+ second_half = ratings[mid:]
146
+
147
+ first_avg = sum(r for r, _ in first_half) / len(first_half) if first_half else 0
148
+ second_avg = sum(r for r, _ in second_half) / len(second_half) if second_half else 0
149
+
150
+ if abs(second_avg - first_avg) >= 0.5:
151
+ direction = "improving" if second_avg > first_avg else "declining"
152
+ signals.append({
153
+ "type": "rating_trend",
154
+ "direction": direction,
155
+ "first_half_avg": round(first_avg, 2),
156
+ "second_half_avg": round(second_avg, 2),
157
+ })
158
+
159
+ # Look for keyword patterns in notes
160
+ all_notes = " ".join(n.lower() for _, n in ratings if n)
161
+ style_keywords = {
162
+ "inquiry": "inquiry_based",
163
+ "socratic": "socratic",
164
+ "direct": "direct_instruction",
165
+ "project": "project_based",
166
+ "workshop": "workshop",
167
+ }
168
+ for keyword, style in style_keywords.items():
169
+ if all_notes.count(keyword) >= 3:
170
+ signals.append({
171
+ "type": "style_keyword",
172
+ "keyword": keyword,
173
+ "suggested_style": style,
174
+ "occurrences": all_notes.count(keyword),
175
+ })
176
+
177
+ return signals
178
+
179
+
180
+ # ── Public API ─────────────────────────────────────────────────────
181
+
182
+ def record_ingestion_changes(
183
+ old_persona: TeacherPersona,
184
+ new_persona: TeacherPersona,
185
+ ) -> list[dict[str, Any]]:
186
+ """Record persona changes detected during file ingestion.
187
+
188
+ Compares old and new personas. If a candidate for the same
189
+ field+new_value already exists, increments its confirmation count
190
+ instead of adding a duplicate. Persists to disk.
191
+ """
192
+ new_candidates = _build_candidate_changes(old_persona, new_persona, source="ingestion")
193
+ if not new_candidates:
194
+ return []
195
+
196
+ existing = _load_candidates()
197
+ now_iso = datetime.now(timezone.utc).isoformat()
198
+
199
+ for nc in new_candidates:
200
+ # Look for an existing candidate with the same field and new_value
201
+ matched = False
202
+ for ec in existing:
203
+ if ec["field"] == nc["field"] and ec["new_value"] == nc["new_value"]:
204
+ ec["confirmations"] += 1
205
+ ec["last_seen"] = now_iso
206
+ matched = True
207
+ break
208
+ if not matched:
209
+ existing.append(nc)
210
+
211
+ _save_candidates(existing)
212
+ logger.info(
213
+ "Recorded %d persona change candidate(s) from ingestion",
214
+ len(new_candidates),
215
+ )
216
+ return new_candidates
217
+
218
+
219
+ def get_confirmed_changes() -> list[dict[str, Any]]:
220
+ """Return candidates that have reached the confirmation threshold."""
221
+ candidates = _load_candidates()
222
+ return [c for c in candidates if c.get("confirmations", 0) >= _CONFIRMATION_THRESHOLD]
223
+
224
+
225
+ def apply_confirmed_changes(
226
+ persona: TeacherPersona,
227
+ ) -> tuple[TeacherPersona, list[str]]:
228
+ """Apply confirmed changes to a persona and clear them from candidates.
229
+
230
+ Returns ``(updated_persona, list_of_change_descriptions)``.
231
+ """
232
+ confirmed = get_confirmed_changes()
233
+ if not confirmed:
234
+ return persona, []
235
+
236
+ # Work on a mutable dict representation
237
+ data = persona.model_dump()
238
+ descriptions: list[str] = []
239
+ applied_fields: set[str] = set()
240
+
241
+ for change in confirmed:
242
+ field = change["field"]
243
+ new_value = change["new_value"]
244
+ old_value = change.get("old_value", "unknown")
245
+
246
+ if field not in _EVOLVABLE_FIELDS:
247
+ continue
248
+
249
+ data[field] = new_value
250
+ applied_fields.add(field)
251
+
252
+ # Human-readable description
253
+ old_display = old_value if not isinstance(old_value, list) else ", ".join(str(v) for v in old_value)
254
+ new_display = new_value if not isinstance(new_value, list) else ", ".join(str(v) for v in new_value)
255
+ descriptions.append(f"{field}: {old_display} -> {new_display}")
256
+
257
+ if not descriptions:
258
+ return persona, []
259
+
260
+ updated = TeacherPersona.model_validate(data)
261
+
262
+ # Remove applied candidates from the file
263
+ candidates = _load_candidates()
264
+ remaining = [
265
+ c for c in candidates
266
+ if not (c["field"] in applied_fields and c.get("confirmations", 0) >= _CONFIRMATION_THRESHOLD)
267
+ ]
268
+ _save_candidates(remaining)
269
+
270
+ logger.info("Applied %d persona evolution(s): %s", len(descriptions), "; ".join(descriptions))
271
+ return updated, descriptions
@@ -24,7 +24,11 @@ Create a structured observation-ready lesson plan with these sections:
24
24
  - 1-2 common misconceptions students might have
25
25
  - How the teacher should redirect each misconception (use analogies when possible)
26
26
 
27
- 4. **Teacher Content Knowledge** — 2-3 paragraphs of background information the teacher should know about the topic beyond what's in the lesson. This is the "expert knowledge" section — historical context, historiographic debates, connections to other units.
27
+ 4. **Teacher Content Knowledge** — 2-3 paragraphs of background information the teacher should know about the topic beyond what's in the lesson. Adapt to the subject:
28
+ - For history/social studies: historical context, historiographic debates, connections to other units.
29
+ - For math: common student misconceptions about this concept and how to address them, alternative solution methods, connections to prerequisite and future skills.
30
+ - For science: current scientific understanding beyond the grade level, common student misconceptions, real-world applications of the concept.
31
+ - For ELA: literary criticism perspectives, author background, connections to other texts and genres.
28
32
 
29
33
  ## Output Format
30
34
 
@@ -124,7 +124,11 @@ Every lesson must meet these standards without exception:
124
124
 
125
125
  1. **All materials referenced must be self-contained.** If the lesson says "distribute the Source Analysis Organizer," the organizer must be fully described in the lesson with all column headers, row labels, and instructions. A teacher printing this lesson should never need to create a referenced material from scratch.
126
126
 
127
- 2. **Primary sources must be quoted in full.** If using a primary source, include the complete excerpt with attribution (author, date, title). Do not write "[Insert primary source here]" — find or compose an appropriate excerpt.
127
+ 2. **Subject-specific content standards:**
128
+ - For history and social studies: Primary sources must be quoted in full with attribution (author, date, title). Do not write "[Insert primary source here]."
129
+ - For math: Worked examples must be fully solved step-by-step with think-aloud annotations. Do not write "students will solve equations" — show the actual equations with solutions.
130
+ - For science: Lab procedures must include specific measurements, materials quantities, and expected observations. Phenomenon descriptions must be concrete, not abstract.
131
+ - For ELA: Include the actual passage, poem excerpt, or mentor sentence — not "students will read a text."
128
132
 
129
133
  3. **Transitions must be scripted.** Between every section, write a 1-2 sentence transition the teacher can say aloud. "OK, now that we've..." not just a section heading.
130
134
 
@@ -83,6 +83,15 @@ Carefully read ALL provided documents and identify:
83
83
  - "Every transition includes a 'connection question' that links the previous section to the next"
84
84
  - "Ends every class with 'one word — what's the one word you're taking away from today?'"
85
85
 
86
+ 19. **Handout Style** — Describe how this teacher's student-facing handouts/worksheets are structured. What format do students receive? Look for patterns like:
87
+ - Dense text packets with primary source excerpts and marginal annotations
88
+ - Fill-in-the-blank guided notes that track the lecture
89
+ - Graphic organizer-heavy layouts with minimal text
90
+ - Station-based packets with documents and analysis questions
91
+ - Simple question-and-answer worksheets
92
+ - Creative/visual layouts with image hooks
93
+ If the materials don't reveal a clear handout style, leave this as an empty string.
94
+
86
95
  ## Output Format
87
96
 
88
97
  Respond with ONLY a JSON object (no markdown fencing, no explanation):
@@ -126,7 +135,8 @@ Respond with ONLY a JSON object (no markdown fencing, no explanation):
126
135
  "Reads primary sources aloud with dramatic emphasis before analysis",
127
136
  "Uses 'connection questions' between every section to link ideas",
128
137
  "Ends class with 'one word takeaway' — every student says one word as they walk out"
129
- ]
138
+ ],
139
+ "handout_style": "Station-based packets with 2-4 primary source documents, context paragraphs, and scaffolded analysis questions building from identify to evaluate"
130
140
  }
131
141
 
132
142
  ## Documents to Analyze
@@ -8,6 +8,9 @@ The packet should be 4-6 pages when printed. Every section has generous response
8
8
  ## Teacher Persona
9
9
  {persona}
10
10
 
11
+ ## Handout Style
12
+ {handout_style_block}
13
+
11
14
  ## Instructions
12
15
 
13
16
  Create a student packet with these sections IN ORDER:
@@ -27,6 +30,13 @@ Create a student packet with these sections IN ORDER:
27
30
  - 3-4 analysis questions that build in complexity (identify → analyze → evaluate)
28
31
  - Each question needs 4-5 response lines
29
32
 
33
+ **Subject Adaptation for Stations:**
34
+ - History / Social Studies: Use primary source documents with full text, context, and sourcing questions.
35
+ - Math: Use problem sets with worked examples at increasing difficulty. Each station covers a different problem type or approach.
36
+ - Science: Use data tables, lab observations, or phenomenon descriptions. Each station presents different data to analyze.
37
+ - ELA: Use different text excerpts (poetry, prose, nonfiction) with comprehension and analysis questions.
38
+ Match the station format to the subject. Do not use primary source document analysis for a math or science lesson.
39
+
30
40
  5. **Graphic Organizer** — A table students fill in as they work through the stations or discussion. Define the column headers and number of rows. Common formats:
31
41
  - Document / Author's Claim / Evidence / Significance
32
42
  - Cause / Event / Effect