clawed 2.3.1__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.
- {clawed-2.3.1 → clawed-2.3.3}/PKG-INFO +1 -1
- {clawed-2.3.1 → clawed-2.3.3}/clawed/__init__.py +1 -1
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/generate_lesson_bundle.py +65 -10
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/update_soul.py +11 -2
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_pptx.py +32 -4
- {clawed-2.3.1 → clawed-2.3.3}/clawed/llm.py +26 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/memory_engine.py +29 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/models.py +9 -0
- clawed-2.3.3/clawed/persona_evolution.py +271 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/admin_lesson_plan.txt +5 -1
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/lesson_plan.txt +5 -1
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/persona_extract.txt +11 -1
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/student_packet.txt +10 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/reading_report.py +186 -2
- {clawed-2.3.1 → clawed-2.3.3}/clawed/slide_images.py +29 -3
- clawed-2.3.3/clawed/voice_check.py +192 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/workspace.py +110 -0
- {clawed-2.3.1 → clawed-2.3.3}/pyproject.toml +1 -1
- {clawed-2.3.1 → clawed-2.3.3}/.gitignore +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/LICENSE +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/README.md +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/__main__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/_legacy_gateway.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/approvals.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/autonomy.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/context.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/core.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/custom_tools.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/drive/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/drive/auth.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/drive/client.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/fake_llm.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/loop.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/curriculum.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/curriculum_kb.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/embeddings.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/episodes.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/identity.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/loader.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/memory/preferences.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/planner.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/prompt.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/scheduler.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/base.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/configure_profile.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/curriculum_map.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_create_doc.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_create_slides.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_list.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_organize.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_read.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/drive_upload.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/export_document.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/gap_analysis.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/generate_assessment.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/generate_lesson.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/generate_materials.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/generate_unit.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/ingest_materials.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/parent_comm.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/read_heartbeat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/read_workspace.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/request_approval.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/schedule_task.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/search_lessons.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/search_my_materials.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/search_standards.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/student_insights.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/sub_packet.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/agent_core/tools/switch_model.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/analytics.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/deps.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/chat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/export.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/feedback.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/gateway_chat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/generate.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/ingest.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/lessons.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/school.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/settings.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/routes/tools.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/server.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/static/app.js +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/static/style.css +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/static/widget.js +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/analytics.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/base.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/dashboard.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/generate.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/index.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/lesson.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/profile.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/settings.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/stats.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/api/templates/students.html +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/assessment.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/asset_registry.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/auth/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/auth/google_auth.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/bot_state.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/chat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/cli.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/cli_chat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/_helpers.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/bot.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/config.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/config_llm.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/config_profile.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/export.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/generate.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/generate_assessment.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/generate_unit.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/queue.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/schedule_cmd.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/sub.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/commands/workspace_cmd.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/config.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/corpus.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/curriculum_map.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/database.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/demo/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/demo/demo_assessment.json +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/demo/demo_lesson_science_g6.json +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/demo/demo_unit_plan.json +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/differentiation.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/doc_export.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/drive.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/evaluation.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_docx.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_handout.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_markdown.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_pdf.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_templates.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/export_theme.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/exporter.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/feedback.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/formats/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/formats/flipchart.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/formats/notebook.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/formats/xbk.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/gateway.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/gateway_response.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/generation.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/export.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/feedback.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/gaps.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/generate.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/ingest.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/misc.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/onboard.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/schedule.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/handlers/standards.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/improver.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/ingestor.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/io.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/lesson.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/materials.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/mcp_server.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/model_router.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/onboarding.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/openclaw_plugin.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/parent_comm.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/persona.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/planner.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/504_accommodations.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/assessment.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/curriculum_gaps.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/dbq_assessment.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/differentiation.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/formative_assessment.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/iep_modification.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/pacing_guide.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/parent_note.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/quiz.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/rubric.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/sub_packet.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/summative_assessment.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/tiered_assignments.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/unit_plan.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/worksheet.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/prompts/year_map.txt +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/quality.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/router.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/sanitize.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/scheduler.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/school.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/search.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/art.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/base.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/computer_science.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/ela.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/foreign_language.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/history.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/library.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/math.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/music.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/physical_education.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/science.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/social_studies.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/skills/special_education.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/standards.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/state.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/state_standards.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/student_bot.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/student_cli.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/student_telegram_bot.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/sub_packet.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/task_queue.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/templates_lib.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/tools.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/__init__.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/cli.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/openclaw.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/student_telegram.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/telegram.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/transports/web.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/tui.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/tui_chat.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/clawed/voice.py +0 -0
- {clawed-2.3.1 → clawed-2.3.3}/eduagent/__init__.py +0 -0
- {clawed-2.3.1 → 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.
|
|
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.
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
lines.append("
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -555,7 +555,7 @@ def export_lesson_pptx(
|
|
|
555
555
|
run.text = "Standards Addressed"
|
|
556
556
|
_set_text_props(run, 16, "888888", bold=True)
|
|
557
557
|
|
|
558
|
-
for std in lesson.standards[:
|
|
558
|
+
for std in lesson.standards[:3]:
|
|
559
559
|
p = tf.add_paragraph()
|
|
560
560
|
p.space_before = Pt(6)
|
|
561
561
|
run = p.add_run()
|
|
@@ -597,9 +597,22 @@ def export_lesson_pptx(
|
|
|
597
597
|
p = tf.paragraphs[0]
|
|
598
598
|
p.line_spacing = Pt(38)
|
|
599
599
|
run = p.add_run()
|
|
600
|
-
|
|
600
|
+
# Brief prompt on slide face, full text in speaker notes
|
|
601
|
+
dn_text = lesson.do_now
|
|
602
|
+
if len(dn_text) > 250:
|
|
603
|
+
cutoff = dn_text[:250].rfind(". ")
|
|
604
|
+
dn_display = dn_text[:cutoff + 1] if cutoff > 80 else dn_text[:250].rsplit(" ", 1)[0] + "..."
|
|
605
|
+
else:
|
|
606
|
+
dn_display = dn_text
|
|
607
|
+
run.text = dn_display
|
|
601
608
|
_set_text_props(run, 28, theme["text_dark"])
|
|
602
609
|
|
|
610
|
+
# Full Do Now in speaker notes
|
|
611
|
+
if len(dn_text) > 250:
|
|
612
|
+
notes_slide = slide.notes_slide
|
|
613
|
+
notes_tf = notes_slide.notes_text_frame
|
|
614
|
+
notes_tf.text = dn_text
|
|
615
|
+
|
|
603
616
|
if do_now_img:
|
|
604
617
|
from clawed.slide_images import _extract_key_concepts
|
|
605
618
|
concepts = _extract_key_concepts(lesson.do_now)
|
|
@@ -1028,7 +1041,11 @@ def export_lesson_pptx(
|
|
|
1028
1041
|
tf = tb.text_frame
|
|
1029
1042
|
tf.word_wrap = True
|
|
1030
1043
|
run = tf.paragraphs[0].add_run()
|
|
1031
|
-
|
|
1044
|
+
# Truncate long questions for slide readability
|
|
1045
|
+
q_text = q.question
|
|
1046
|
+
if len(q_text) > 120:
|
|
1047
|
+
q_text = q_text[:120].rsplit(" ", 1)[0] + "..."
|
|
1048
|
+
run.text = q_text
|
|
1032
1049
|
_set_text_props(run, 20, theme["text_dark"])
|
|
1033
1050
|
|
|
1034
1051
|
q_top += Inches(1.3)
|
|
@@ -1081,8 +1098,19 @@ def export_lesson_pptx(
|
|
|
1081
1098
|
p = tf.paragraphs[0]
|
|
1082
1099
|
p.line_spacing = Pt(30)
|
|
1083
1100
|
run = p.add_run()
|
|
1084
|
-
|
|
1101
|
+
# Brief homework summary on slide, full in notes
|
|
1102
|
+
hw_text = lesson.homework
|
|
1103
|
+
if len(hw_text) > 200:
|
|
1104
|
+
cutoff = hw_text[:200].rfind(". ")
|
|
1105
|
+
hw_display = hw_text[:cutoff + 1] if cutoff > 60 else hw_text[:200].rsplit(" ", 1)[0] + "..."
|
|
1106
|
+
else:
|
|
1107
|
+
hw_display = hw_text
|
|
1108
|
+
run.text = hw_display
|
|
1085
1109
|
_set_text_props(run, 22, "DDDDDD")
|
|
1110
|
+
if len(hw_text) > 200:
|
|
1111
|
+
notes_slide = slide.notes_slide
|
|
1112
|
+
notes_tf = notes_slide.notes_text_frame
|
|
1113
|
+
notes_tf.text = f"HOMEWORK (full text):\n{hw_text}"
|
|
1086
1114
|
else:
|
|
1087
1115
|
# "Key Takeaway" or "Questions?"
|
|
1088
1116
|
tb = slide.shapes.add_textbox(
|
|
@@ -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.
|
|
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. **
|
|
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
|