claude-smart 0.2.42 → 0.2.44
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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +1 -1
- package/bin/claude-smart.js +2 -2
- package/package.json +9 -3
- package/plugin/.claude-plugin/plugin.json +9 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +23 -3
- package/plugin/pyproject.toml +3 -3
- package/plugin/scripts/_lib.sh +91 -0
- package/plugin/scripts/backend-service.sh +51 -4
- package/plugin/scripts/cli.sh +3 -1
- package/plugin/scripts/codex-hook.js +72 -4
- package/plugin/scripts/dashboard-build.sh +1 -0
- package/plugin/scripts/dashboard-service.sh +1 -0
- package/plugin/scripts/ensure-plugin-root.sh +1 -0
- package/plugin/scripts/hook_entry.sh +6 -3
- package/plugin/scripts/smart-install.sh +3 -2
- package/plugin/src/README.md +57 -0
- package/plugin/src/claude_smart/context_format.py +11 -12
- package/plugin/src/claude_smart/cs_cite.py +26 -12
- package/plugin/src/claude_smart/ids.py +13 -5
- package/plugin/uv.lock +126 -5
- package/plugin/vendor/reflexio/.env.example +62 -0
- package/plugin/vendor/reflexio/LICENSE +201 -0
- package/plugin/vendor/reflexio/README.md +338 -0
- package/plugin/vendor/reflexio/pyproject.toml +274 -0
- package/plugin/vendor/reflexio/reflexio/README.md +184 -0
- package/plugin/vendor/reflexio/reflexio/__init__.py +166 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/README.md +109 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/backends.py +175 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/bench.py +642 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/embed_cache.py +330 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/report.py +317 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/report.md +43 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/results/results.json +4478 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/scenarios.py +134 -0
- package/plugin/vendor/reflexio/reflexio/benchmarks/retrieval_latency/seed.py +255 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +287 -0
- package/plugin/vendor/reflexio/reflexio/cli/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +56 -0
- package/plugin/vendor/reflexio/reflexio/cli/_client.py +86 -0
- package/plugin/vendor/reflexio/reflexio/cli/app.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +265 -0
- package/plugin/vendor/reflexio/reflexio/cli/codex_auth.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/admin_cmd.py +65 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/agent_playbooks.py +503 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/api.py +114 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/auth.py +109 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/config_cmd.py +511 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/doctor.py +127 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/embeddings.py +53 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +478 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +303 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +289 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +964 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +285 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/status_cmd.py +143 -0
- package/plugin/vendor/reflexio/reflexio/cli/commands/user_playbooks.py +373 -0
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +284 -0
- package/plugin/vendor/reflexio/reflexio/cli/errors.py +217 -0
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +247 -0
- package/plugin/vendor/reflexio/reflexio/cli/output.py +867 -0
- package/plugin/vendor/reflexio/reflexio/cli/paths.py +41 -0
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +391 -0
- package/plugin/vendor/reflexio/reflexio/cli/state.py +204 -0
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +96 -0
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +329 -0
- package/plugin/vendor/reflexio/reflexio/client/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/client/cache.py +150 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +2613 -0
- package/plugin/vendor/reflexio/reflexio/defaults.py +23 -0
- package/plugin/vendor/reflexio/reflexio/integrations/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/.clawhubignore +7 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +274 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +517 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +473 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +2156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +241 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +113 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +103 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +156 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +134 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +17 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +24 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +278 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +164 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +27 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +91 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/README.md +120 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/TESTING.md +168 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package-lock.json +1657 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/package.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/HEARTBEAT.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/README.md +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/_meta.json +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/agents/reflexio-extractor.md +45 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/handler.ts +214 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/hook/setup.ts +55 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/index.ts +327 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/consolidate.ts +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/dedup.ts +80 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/io.ts +155 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/openclaw-cli.ts +67 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/search.ts +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-playbook.ts +76 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/lib/write-profile.ts +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/openclaw.plugin.json +46 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/package.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/README.md +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/full_consolidation.md +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/playbook_extraction.md +217 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/prompts/profile_extraction.md +132 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-consolidate/SKILL.md +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/plugin/skills/reflexio-embedded/SKILL.md +194 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/HOOK.md +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/architecture.md +49 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/comparison.md +31 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/future-work.md +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/references/porting-notes.md +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/install.sh +52 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/scripts/uninstall.sh +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/consolidate.test.ts +135 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/dedup.test.ts +104 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/io.test.ts +175 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/search.test.ts +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/smoke-test.ts +140 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-playbook.test.ts +93 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tests/write-profile.test.ts +174 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/tsconfig.json +16 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/types/openclaw.d.ts +230 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw-embedded/vitest.config.ts +7 -0
- package/plugin/vendor/reflexio/reflexio/lib/__init__.py +23 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +310 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +225 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +83 -0
- package/plugin/vendor/reflexio/reflexio/lib/_dashboard.py +266 -0
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +176 -0
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +334 -0
- package/plugin/vendor/reflexio/reflexio/lib/_operations.py +153 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +545 -0
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +52 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +167 -0
- package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +103 -0
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +288 -0
- package/plugin/vendor/reflexio/reflexio/lib/reflexio_lib.py +27 -0
- package/plugin/vendor/reflexio/reflexio/models/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/braintrust_schema.py +141 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/common.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +1112 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +63 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +487 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +28 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +83 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +768 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/service_schemas.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/stall_state_schema.py +32 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/__init__.py +3 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +177 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +129 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/enums.py +25 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +333 -0
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +908 -0
- package/plugin/vendor/reflexio/reflexio/models/py.typed +0 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +90 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +622 -0
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +2868 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/account_api.py +143 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/health_api.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/pending_tool_call_api.py +572 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +562 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/stall_state_api.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/__init__.py +15 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +208 -0
- package/plugin/vendor/reflexio/reflexio/server/correlation.py +46 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/__init__.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +359 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/image_utils.py +55 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1871 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +140 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +479 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1122 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_stream_parser.py +197 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +338 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +187 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/llm_reranker.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +716 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/_dispatchers.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/README.md +121 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation/v1.0.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/agent_success_evaluation_with_comparison/v1.0.0.prompt.md +76 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/answer_synthesis/v1.5.2.prompt.md +88 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/compress_session_for_query/v1.3.0.prompt.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +20 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.0.0.prompt.md +53 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.1.0.prompt.md +57 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.2.0.prompt.md +68 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.3.0.prompt.md +70 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.4.0.prompt.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.5.0.prompt.md +82 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +83 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.1.0.prompt.md +193 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +206 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0-deprecated.prompt.md +66 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v1.1.0.prompt.md +46 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0-deprecated.prompt.md +64 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.1.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.2.0.prompt.md +47 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.0.2.prompt.md +254 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.1.0.prompt.md +274 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +283 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v1.0.0.prompt.md +73 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v2.0.0.prompt.md +86 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.0.0.prompt.md +97 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.1.0.prompt.md +119 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.2.0.prompt.md +123 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.1.0.prompt.md +24 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +29 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.0.0.prompt.md +11 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.1.0.prompt.md +21 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main_expert/v1.2.0.prompt.md +25 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.0.0.prompt.md +37 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.1.0.prompt.md +40 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_optimizer_judge/v1.2.0.prompt.md +36 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v1.0.0.prompt.md +45 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v2.0.0.prompt.md +81 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate/v3.0.0.prompt.md +80 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_should_generate_expert/v1.0.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_deduplication/v1.0.0.prompt.md +116 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_should_generate_override/v1.0.0.prompt.md +16 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +140 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +160 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_main/v1.0.0.prompt.md +14 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +19 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/rerank_relevance/v1.1.0.prompt.md +44 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_content_evaluation/v1.0.0.prompt.md +33 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/feedback_extraction_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_evaluation/prompt_evaluation_dataset/profile_update_main_v1.jsonl +10 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_schema.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +131 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_service.py +228 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluator.py +372 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/delayed_group_evaluator.py +156 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +336 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +471 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +1668 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_cron.py +196 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/_encryption.py +101 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/client.py +167 -0
- package/plugin/vendor/reflexio/reflexio/server/services/braintrust/service.py +281 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/config_storage.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/configurator.py +87 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/local_file_config_storage.py +187 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/test_config_storage.py +162 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +112 -0
- package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/distribution.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +126 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +192 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/hero_state.py +75 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/rule_attribution.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +515 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/shadow_aggregation.py +90 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +91 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +303 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +358 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +138 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +535 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +779 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +1125 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +94 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +251 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +702 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +835 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +89 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_aggregator.py +1388 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +1045 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_extractor.py +436 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_generation_service.py +808 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_constants.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +362 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/__init__.py +24 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/assistant_webhook.py +246 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/gepa_adapter.py +291 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/judge.py +97 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +96 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +645 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/rollout.py +35 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scenario_resolver.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +174 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/__init__.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_document_expander.py +179 -0
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +297 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +772 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_extractor.py +462 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +737 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +290 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_extractor.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +803 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +146 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +80 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +756 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +81 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/constants.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +11 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +155 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +1298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +1945 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +600 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +346 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +747 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +166 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +217 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +153 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +384 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +71 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +235 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +170 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +677 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +250 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +154 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +93 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_stall_state.py +76 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +572 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +77 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +116 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_manager.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +13 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/llm_model_setting.json +7 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +158 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +113 -0
- package/plugin/vendor/reflexio/reflexio/server/uvicorn_logging.py +76 -0
- package/plugin/vendor/reflexio/reflexio/test_support/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_fixtures.py +62 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +242 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +129 -0
- package/plugin/vendor/reflexio/reflexio/test_support/skip_decorators.py +43 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
"""Sliding-window reflection: critique-and-revise of cited memories.
|
|
2
|
+
|
|
3
|
+
Invoked from ``GenerationService.run`` after the publish has saved its
|
|
4
|
+
interactions and before the extractor pool spins up. Mirrors the
|
|
5
|
+
extractor pattern: window of size ``window_size`` (global), advanced
|
|
6
|
+
every ``stride_size`` interactions per ``OperationStateManager``
|
|
7
|
+
bookmark. When the gate is open and at least one Assistant interaction
|
|
8
|
+
in the window cites a *current* user playbook / user profile row, the
|
|
9
|
+
service asks an LLM whether any of those cited rows should be replaced
|
|
10
|
+
in light of how they were applied across the window. Replacements
|
|
11
|
+
archive the cited row and insert a new current row with copied identity
|
|
12
|
+
and metadata.
|
|
13
|
+
|
|
14
|
+
Runs *before* extraction on purpose: extractors then read the
|
|
15
|
+
post-reflection state when computing existing-data context.
|
|
16
|
+
|
|
17
|
+
Failure isolation:
|
|
18
|
+
- Per-decision apply errors are caught locally so one bad decision
|
|
19
|
+
cannot block the others.
|
|
20
|
+
- Top-level failures (extractor LLM call) are caught and logged; the
|
|
21
|
+
bookmark is *not* advanced so the next publish retries the window.
|
|
22
|
+
- The caller (``GenerationService.run``) wraps reflection in its own
|
|
23
|
+
try/except so a reflection bug never breaks the publish.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import uuid
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from datetime import UTC, datetime
|
|
32
|
+
from typing import TYPE_CHECKING
|
|
33
|
+
|
|
34
|
+
from reflexio.models.api_schema.domain.entities import (
|
|
35
|
+
Citation,
|
|
36
|
+
Interaction,
|
|
37
|
+
UserPlaybook,
|
|
38
|
+
UserProfile,
|
|
39
|
+
)
|
|
40
|
+
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
41
|
+
from reflexio.server.services.operation_state_utils import OperationStateManager
|
|
42
|
+
from reflexio.server.services.reflection.reflection_extractor import (
|
|
43
|
+
ReflectionExtractor,
|
|
44
|
+
)
|
|
45
|
+
from reflexio.server.services.reflection.reflection_service_utils import (
|
|
46
|
+
REFLECTION_OPERATION_NAME,
|
|
47
|
+
ReflectionDecision,
|
|
48
|
+
ReflectionResult,
|
|
49
|
+
ReflectionServiceRequest,
|
|
50
|
+
)
|
|
51
|
+
from reflexio.server.tracing import sentry_tags
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from reflexio.models.api_schema.internal_schema import (
|
|
55
|
+
RequestInteractionDataModel,
|
|
56
|
+
)
|
|
57
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
58
|
+
|
|
59
|
+
logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
# Fallback per-pass revision cap when no ReflectionConfig is available in
|
|
62
|
+
# the apply path. Mirrors ReflectionConfig.max_revisions_per_pass default.
|
|
63
|
+
_DEFAULT_MAX_REVISIONS_PER_PASS = 8
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ReflectionService:
|
|
67
|
+
"""Sliding-window reflection step.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
request_context (RequestContext): Provides storage, prompt
|
|
71
|
+
manager, and configurator.
|
|
72
|
+
llm_client (LiteLLMClient): Shared LLM client.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
request_context: RequestContext,
|
|
78
|
+
llm_client: LiteLLMClient,
|
|
79
|
+
):
|
|
80
|
+
self.request_context = request_context
|
|
81
|
+
self.client = llm_client
|
|
82
|
+
|
|
83
|
+
def run(self, request: ReflectionServiceRequest) -> ReflectionResult:
|
|
84
|
+
"""Execute one reflection pass.
|
|
85
|
+
|
|
86
|
+
Always returns a ``ReflectionResult`` describing what happened.
|
|
87
|
+
Routine failure modes (gate closed, no citations, missing rows,
|
|
88
|
+
LLM error) do not raise. Storage exceptions propagate.
|
|
89
|
+
"""
|
|
90
|
+
result = ReflectionResult()
|
|
91
|
+
config = self.request_context.configurator.get_config()
|
|
92
|
+
reflection_config = config.reflection_config if config else None
|
|
93
|
+
if reflection_config is not None and not reflection_config.enabled:
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
storage = self.request_context.storage
|
|
97
|
+
if storage is None:
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
window_size = config.window_size if config else 10
|
|
101
|
+
stride_size = config.stride_size if config else 5
|
|
102
|
+
sources = [request.source] if request.source else None
|
|
103
|
+
|
|
104
|
+
mgr = OperationStateManager(
|
|
105
|
+
storage, # type: ignore[arg-type]
|
|
106
|
+
self.request_context.org_id,
|
|
107
|
+
REFLECTION_OPERATION_NAME,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Gate: how many new interactions since last bookmark?
|
|
111
|
+
_, new_models = mgr.get_extractor_state_with_new_interactions(
|
|
112
|
+
REFLECTION_OPERATION_NAME,
|
|
113
|
+
user_id=request.user_id,
|
|
114
|
+
sources=sources,
|
|
115
|
+
)
|
|
116
|
+
new_count = sum(len(m.interactions) for m in new_models)
|
|
117
|
+
if new_count < stride_size:
|
|
118
|
+
return result
|
|
119
|
+
result.gate_open = True
|
|
120
|
+
|
|
121
|
+
# Pull window of last window_size interactions for the user.
|
|
122
|
+
window_models, _flat = storage.get_last_k_interactions_grouped(
|
|
123
|
+
user_id=request.user_id,
|
|
124
|
+
k=window_size,
|
|
125
|
+
sources=sources,
|
|
126
|
+
)
|
|
127
|
+
window_interactions = _flatten(window_models)
|
|
128
|
+
if not window_interactions:
|
|
129
|
+
mgr.update_extractor_bookmark(
|
|
130
|
+
REFLECTION_OPERATION_NAME,
|
|
131
|
+
processed_interactions=[],
|
|
132
|
+
user_id=request.user_id,
|
|
133
|
+
)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
citations = _collect_citations(window_interactions)
|
|
137
|
+
result.cited_count = len(citations)
|
|
138
|
+
if not citations:
|
|
139
|
+
# Advance the bookmark — we did examine this window.
|
|
140
|
+
mgr.update_extractor_bookmark(
|
|
141
|
+
REFLECTION_OPERATION_NAME,
|
|
142
|
+
processed_interactions=window_interactions,
|
|
143
|
+
user_id=request.user_id,
|
|
144
|
+
)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
# Post-horizon filter: only send citations with enough follow-up context.
|
|
148
|
+
post_horizon_size = (
|
|
149
|
+
reflection_config.post_horizon_size if reflection_config else 3
|
|
150
|
+
)
|
|
151
|
+
eligible = _filter_citations_by_horizon(
|
|
152
|
+
citations=citations,
|
|
153
|
+
window=window_interactions,
|
|
154
|
+
post_horizon_size=post_horizon_size,
|
|
155
|
+
stride_size=stride_size,
|
|
156
|
+
)
|
|
157
|
+
deferred_count = len(citations) - len(eligible)
|
|
158
|
+
result.skipped_count += deferred_count
|
|
159
|
+
if not eligible:
|
|
160
|
+
mgr.update_extractor_bookmark(
|
|
161
|
+
REFLECTION_OPERATION_NAME,
|
|
162
|
+
processed_interactions=window_interactions,
|
|
163
|
+
user_id=request.user_id,
|
|
164
|
+
)
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
eligible_citations = [e.citation for e in eligible]
|
|
168
|
+
horizon_by_key = {
|
|
169
|
+
(e.citation.kind, e.citation.real_id): e.has_full_horizon for e in eligible
|
|
170
|
+
}
|
|
171
|
+
cited_profiles, cited_playbooks, missing = self._resolve_cited_rows(
|
|
172
|
+
user_id=request.user_id,
|
|
173
|
+
citations=eligible_citations,
|
|
174
|
+
)
|
|
175
|
+
result.skipped_count += missing
|
|
176
|
+
result.considered_count = len(cited_profiles) + len(cited_playbooks)
|
|
177
|
+
if result.considered_count == 0:
|
|
178
|
+
mgr.update_extractor_bookmark(
|
|
179
|
+
REFLECTION_OPERATION_NAME,
|
|
180
|
+
processed_interactions=window_interactions,
|
|
181
|
+
user_id=request.user_id,
|
|
182
|
+
)
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
agent_context = (config.agent_context_prompt or "") if config else ""
|
|
186
|
+
model_override = reflection_config.model if reflection_config else None
|
|
187
|
+
extractor = ReflectionExtractor(
|
|
188
|
+
request_context=self.request_context,
|
|
189
|
+
llm_client=self.client,
|
|
190
|
+
agent_context=agent_context,
|
|
191
|
+
model_override=model_override,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
output = extractor.run(
|
|
196
|
+
window_interactions=window_interactions,
|
|
197
|
+
cited_profiles=cited_profiles,
|
|
198
|
+
cited_user_playbooks=cited_playbooks,
|
|
199
|
+
horizon_by_key=horizon_by_key,
|
|
200
|
+
)
|
|
201
|
+
except Exception as exc: # noqa: BLE001 — best-effort
|
|
202
|
+
with sentry_tags(
|
|
203
|
+
subsystem="reflection",
|
|
204
|
+
op="extractor",
|
|
205
|
+
org_id=self.request_context.org_id,
|
|
206
|
+
user_id=request.user_id,
|
|
207
|
+
error_type=type(exc).__name__,
|
|
208
|
+
):
|
|
209
|
+
logger.exception(
|
|
210
|
+
"event=reflection_extractor_failed user_id=%s",
|
|
211
|
+
request.user_id,
|
|
212
|
+
)
|
|
213
|
+
# Don't advance bookmark — let the next publish retry.
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
result.ran = True
|
|
217
|
+
profiles_by_id = {p.profile_id: p for p in cited_profiles}
|
|
218
|
+
playbooks_by_id = {p.user_playbook_id: p for p in cited_playbooks}
|
|
219
|
+
|
|
220
|
+
max_revisions_per_pass = (
|
|
221
|
+
reflection_config.max_revisions_per_pass
|
|
222
|
+
if reflection_config is not None
|
|
223
|
+
else _DEFAULT_MAX_REVISIONS_PER_PASS
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
for decision in output.decisions:
|
|
227
|
+
if not _is_revision(decision):
|
|
228
|
+
result.no_change_count += 1
|
|
229
|
+
continue
|
|
230
|
+
# Per-pass cap: once we've applied max_revisions_per_pass
|
|
231
|
+
# revisions, skip any further revision-intent decisions.
|
|
232
|
+
if result.revised_count >= max_revisions_per_pass:
|
|
233
|
+
result.capped_count += 1
|
|
234
|
+
continue
|
|
235
|
+
try:
|
|
236
|
+
self._validate_decision(decision, profiles_by_id, playbooks_by_id)
|
|
237
|
+
applied = self._apply_revision(
|
|
238
|
+
request=request,
|
|
239
|
+
decision=decision,
|
|
240
|
+
profiles_by_id=profiles_by_id,
|
|
241
|
+
playbooks_by_id=playbooks_by_id,
|
|
242
|
+
)
|
|
243
|
+
except Exception as exc: # noqa: BLE001 — per-decision isolation
|
|
244
|
+
result.failed_count += 1
|
|
245
|
+
with sentry_tags(
|
|
246
|
+
subsystem="reflection",
|
|
247
|
+
op="apply_decision",
|
|
248
|
+
org_id=self.request_context.org_id,
|
|
249
|
+
user_id=request.user_id,
|
|
250
|
+
target_kind=decision.target_kind,
|
|
251
|
+
target_id=decision.target_id,
|
|
252
|
+
error_type=type(exc).__name__,
|
|
253
|
+
):
|
|
254
|
+
logger.exception(
|
|
255
|
+
"event=reflection_apply_failed kind=%s target_id=%s",
|
|
256
|
+
decision.target_kind,
|
|
257
|
+
decision.target_id,
|
|
258
|
+
)
|
|
259
|
+
continue
|
|
260
|
+
if applied:
|
|
261
|
+
result.revised_count += 1
|
|
262
|
+
# Field-derivable granular counters (no mode label exists).
|
|
263
|
+
if decision.new_trigger is not None:
|
|
264
|
+
result.trigger_revised_count += 1
|
|
265
|
+
if decision.new_content is not None:
|
|
266
|
+
result.content_revised_count += 1
|
|
267
|
+
if decision.new_profile_time_to_live is not None:
|
|
268
|
+
result.ttl_changed_count += 1
|
|
269
|
+
else:
|
|
270
|
+
result.skipped_count += 1
|
|
271
|
+
|
|
272
|
+
mgr.update_extractor_bookmark(
|
|
273
|
+
REFLECTION_OPERATION_NAME,
|
|
274
|
+
processed_interactions=window_interactions,
|
|
275
|
+
user_id=request.user_id,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
logger.info(
|
|
279
|
+
"event=reflection_done user_id=%s gate_open=%s ran=%s "
|
|
280
|
+
"cited=%d considered=%d no_change=%d revised=%d "
|
|
281
|
+
"trigger_revised=%d content_revised=%d ttl_changed=%d capped=%d "
|
|
282
|
+
"skipped=%d failed=%d",
|
|
283
|
+
request.user_id,
|
|
284
|
+
result.gate_open,
|
|
285
|
+
result.ran,
|
|
286
|
+
result.cited_count,
|
|
287
|
+
result.considered_count,
|
|
288
|
+
result.no_change_count,
|
|
289
|
+
result.revised_count,
|
|
290
|
+
result.trigger_revised_count,
|
|
291
|
+
result.content_revised_count,
|
|
292
|
+
result.ttl_changed_count,
|
|
293
|
+
result.capped_count,
|
|
294
|
+
result.skipped_count,
|
|
295
|
+
result.failed_count,
|
|
296
|
+
)
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
# Helpers
|
|
301
|
+
# ------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
def _resolve_cited_rows(
|
|
304
|
+
self,
|
|
305
|
+
*,
|
|
306
|
+
user_id: str,
|
|
307
|
+
citations: list[Citation],
|
|
308
|
+
) -> tuple[list[UserProfile], list[UserPlaybook], int]:
|
|
309
|
+
"""Resolve citations to current rows; count missing/archived.
|
|
310
|
+
|
|
311
|
+
Citations whose target row has already been archived (e.g. by
|
|
312
|
+
the deduplicator earlier in the publish flow, or by a prior
|
|
313
|
+
reflection pass) are silently skipped — only ``status IS NULL``
|
|
314
|
+
rows are sent to the LLM. Uses ``get_profiles_by_ids`` /
|
|
315
|
+
``get_user_playbooks_by_ids`` so storage filters server-side
|
|
316
|
+
rather than scanning every row for the user.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Tuple of (cited_profiles, cited_playbooks, missing_count).
|
|
320
|
+
"""
|
|
321
|
+
storage = self.request_context.storage
|
|
322
|
+
wanted_profile_ids: set[str] = set()
|
|
323
|
+
wanted_playbook_ids: set[int] = set()
|
|
324
|
+
for c in citations:
|
|
325
|
+
if c.kind == "profile":
|
|
326
|
+
wanted_profile_ids.add(c.real_id)
|
|
327
|
+
elif c.kind == "playbook":
|
|
328
|
+
try:
|
|
329
|
+
wanted_playbook_ids.add(int(c.real_id))
|
|
330
|
+
except (TypeError, ValueError):
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
cited_profiles: list[UserProfile] = []
|
|
334
|
+
if wanted_profile_ids:
|
|
335
|
+
cited_profiles = storage.get_profiles_by_ids( # type: ignore[union-attr]
|
|
336
|
+
user_id=user_id,
|
|
337
|
+
profile_ids=list(wanted_profile_ids),
|
|
338
|
+
status_filter=[None],
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
cited_playbooks: list[UserPlaybook] = []
|
|
342
|
+
if wanted_playbook_ids:
|
|
343
|
+
cited_playbooks = storage.get_user_playbooks_by_ids( # type: ignore[union-attr]
|
|
344
|
+
user_id=user_id,
|
|
345
|
+
user_playbook_ids=list(wanted_playbook_ids),
|
|
346
|
+
status_filter=[None],
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
found = len(cited_profiles) + len(cited_playbooks)
|
|
350
|
+
missing = max(
|
|
351
|
+
0,
|
|
352
|
+
len(wanted_profile_ids) + len(wanted_playbook_ids) - found,
|
|
353
|
+
)
|
|
354
|
+
return cited_profiles, cited_playbooks, missing
|
|
355
|
+
|
|
356
|
+
def _validate_decision(
|
|
357
|
+
self,
|
|
358
|
+
decision: ReflectionDecision,
|
|
359
|
+
profiles_by_id: dict[str, UserProfile], # noqa: ARG002 - kept for signature parity with _apply_revision
|
|
360
|
+
playbooks_by_id: dict[int, UserPlaybook],
|
|
361
|
+
) -> None:
|
|
362
|
+
"""Raise if a playbook content rewrite omits ``new_rationale``.
|
|
363
|
+
|
|
364
|
+
Flip is LLM-reported, not derived: the ``memory_reflection`` prompt
|
|
365
|
+
instructs the model to set ``new_rationale`` whenever it changes a
|
|
366
|
+
rule's orientation (a flip), and to set it on substance rewrites of
|
|
367
|
+
playbook content as well. So any playbook revision that sets
|
|
368
|
+
``new_content`` must carry a ``new_rationale`` — this preserves the
|
|
369
|
+
flip-requires-rationale audit-trail spirit without re-deriving
|
|
370
|
+
polarity from wording. Trigger-only / TTL-only revisions are exempt.
|
|
371
|
+
|
|
372
|
+
Per-decision try/except in the caller catches these and counts
|
|
373
|
+
them as failed_count.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
decision (ReflectionDecision): The decision to validate.
|
|
377
|
+
profiles_by_id (dict[str, UserProfile]): Resolved profile
|
|
378
|
+
rows keyed by profile_id.
|
|
379
|
+
playbooks_by_id (dict[int, UserPlaybook]): Resolved playbook
|
|
380
|
+
rows keyed by user_playbook_id.
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
ValueError: When a playbook ``new_content`` revision omits
|
|
384
|
+
``new_rationale``.
|
|
385
|
+
"""
|
|
386
|
+
if decision.target_kind == "profile":
|
|
387
|
+
return
|
|
388
|
+
# Playbook
|
|
389
|
+
try:
|
|
390
|
+
target_id = int(decision.target_id)
|
|
391
|
+
except (TypeError, ValueError) as exc:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
f"playbook target_id not an int: {decision.target_id!r}"
|
|
394
|
+
) from exc
|
|
395
|
+
cited = playbooks_by_id.get(target_id)
|
|
396
|
+
if cited is None:
|
|
397
|
+
return # apply step will mark as skipped
|
|
398
|
+
if decision.new_content is not None and not decision.new_rationale:
|
|
399
|
+
raise ValueError("playbook content revision must include new_rationale")
|
|
400
|
+
|
|
401
|
+
def _apply_revision(
|
|
402
|
+
self,
|
|
403
|
+
*,
|
|
404
|
+
request: ReflectionServiceRequest,
|
|
405
|
+
decision: ReflectionDecision,
|
|
406
|
+
profiles_by_id: dict[str, UserProfile],
|
|
407
|
+
playbooks_by_id: dict[int, UserPlaybook],
|
|
408
|
+
) -> bool:
|
|
409
|
+
"""Apply a revision decision.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
request (ReflectionServiceRequest): The current service
|
|
413
|
+
request (for user_id / agent_version).
|
|
414
|
+
decision (ReflectionDecision): The revision to apply.
|
|
415
|
+
profiles_by_id (dict[str, UserProfile]): Resolved profile
|
|
416
|
+
rows keyed by profile_id.
|
|
417
|
+
playbooks_by_id (dict[int, UserPlaybook]): Resolved playbook
|
|
418
|
+
rows keyed by user_playbook_id.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
bool: ``applied``. ``False`` means the target row could not be
|
|
422
|
+
resolved (target archived between resolve and apply, etc.).
|
|
423
|
+
|
|
424
|
+
Flip is no longer derived here: orientation changes are LLM-reported
|
|
425
|
+
via the rewritten ``new_content`` + ``new_rationale`` the prompt
|
|
426
|
+
emits, not inferred from wording at apply time.
|
|
427
|
+
"""
|
|
428
|
+
if decision.target_kind == "profile":
|
|
429
|
+
cited_p = profiles_by_id.get(decision.target_id)
|
|
430
|
+
if cited_p is None:
|
|
431
|
+
return False
|
|
432
|
+
return self._replace_profile(request, decision, cited_p)
|
|
433
|
+
# Playbook
|
|
434
|
+
try:
|
|
435
|
+
target_id = int(decision.target_id)
|
|
436
|
+
except (TypeError, ValueError):
|
|
437
|
+
return False
|
|
438
|
+
cited_pb = playbooks_by_id.get(target_id)
|
|
439
|
+
if cited_pb is None:
|
|
440
|
+
return False
|
|
441
|
+
return self._replace_playbook(request, decision, cited_pb)
|
|
442
|
+
|
|
443
|
+
def _replace_profile(
|
|
444
|
+
self,
|
|
445
|
+
request: ReflectionServiceRequest,
|
|
446
|
+
decision: ReflectionDecision,
|
|
447
|
+
cited: UserProfile,
|
|
448
|
+
) -> bool:
|
|
449
|
+
"""Insert the replacement profile, then archive the cited row.
|
|
450
|
+
|
|
451
|
+
Insert-first ordering means that if ``add_user_profile`` raises,
|
|
452
|
+
the cited row stays current and the per-decision exception
|
|
453
|
+
handler reports ``failed_count``. Only after the new row is
|
|
454
|
+
durable do we flip the cited row to ARCHIVED — and if *that*
|
|
455
|
+
fails we log at ERROR rather than silently dropping the user's
|
|
456
|
+
data, leaving a transient duplicate that downstream dedup can
|
|
457
|
+
clean up.
|
|
458
|
+
"""
|
|
459
|
+
storage = self.request_context.storage
|
|
460
|
+
if storage is None:
|
|
461
|
+
return False
|
|
462
|
+
now_ts = int(datetime.now(UTC).timestamp())
|
|
463
|
+
new_profile = UserProfile(
|
|
464
|
+
profile_id=str(uuid.uuid4()),
|
|
465
|
+
user_id=cited.user_id,
|
|
466
|
+
content=decision.new_content or cited.content,
|
|
467
|
+
last_modified_timestamp=now_ts,
|
|
468
|
+
generated_from_request_id=cited.generated_from_request_id,
|
|
469
|
+
profile_time_to_live=(
|
|
470
|
+
decision.new_profile_time_to_live or cited.profile_time_to_live
|
|
471
|
+
),
|
|
472
|
+
custom_features=cited.custom_features,
|
|
473
|
+
source=cited.source,
|
|
474
|
+
status=None,
|
|
475
|
+
extractor_names=cited.extractor_names,
|
|
476
|
+
)
|
|
477
|
+
_log_edit_magnitude(
|
|
478
|
+
kind="profile",
|
|
479
|
+
target_id=cited.profile_id,
|
|
480
|
+
old_content=cited.content,
|
|
481
|
+
new_content=new_profile.content,
|
|
482
|
+
)
|
|
483
|
+
storage.add_user_profile(cited.user_id, [new_profile])
|
|
484
|
+
try:
|
|
485
|
+
archived = storage.archive_profile_by_id(
|
|
486
|
+
user_id=request.user_id, profile_id=cited.profile_id
|
|
487
|
+
)
|
|
488
|
+
except Exception as exc: # noqa: BLE001
|
|
489
|
+
with sentry_tags(
|
|
490
|
+
subsystem="reflection",
|
|
491
|
+
op="archive_after_insert",
|
|
492
|
+
kind="profile",
|
|
493
|
+
org_id=self.request_context.org_id,
|
|
494
|
+
user_id=cited.user_id,
|
|
495
|
+
cited_id=cited.profile_id,
|
|
496
|
+
new_id=new_profile.profile_id,
|
|
497
|
+
error_type=type(exc).__name__,
|
|
498
|
+
):
|
|
499
|
+
logger.exception(
|
|
500
|
+
"event=reflection_archive_after_insert_failed kind=profile "
|
|
501
|
+
"cited_id=%s new_id=%s",
|
|
502
|
+
cited.profile_id,
|
|
503
|
+
new_profile.profile_id,
|
|
504
|
+
)
|
|
505
|
+
return True
|
|
506
|
+
if not archived:
|
|
507
|
+
with sentry_tags(
|
|
508
|
+
subsystem="reflection",
|
|
509
|
+
op="archive_after_insert_noop",
|
|
510
|
+
kind="profile",
|
|
511
|
+
org_id=self.request_context.org_id,
|
|
512
|
+
user_id=cited.user_id,
|
|
513
|
+
cited_id=cited.profile_id,
|
|
514
|
+
new_id=new_profile.profile_id,
|
|
515
|
+
):
|
|
516
|
+
logger.error(
|
|
517
|
+
"event=reflection_archive_after_insert_noop kind=profile "
|
|
518
|
+
"cited_id=%s new_id=%s",
|
|
519
|
+
cited.profile_id,
|
|
520
|
+
new_profile.profile_id,
|
|
521
|
+
)
|
|
522
|
+
return True
|
|
523
|
+
|
|
524
|
+
def _replace_playbook(
|
|
525
|
+
self,
|
|
526
|
+
request: ReflectionServiceRequest,
|
|
527
|
+
decision: ReflectionDecision,
|
|
528
|
+
cited: UserPlaybook,
|
|
529
|
+
) -> bool:
|
|
530
|
+
"""Insert the replacement playbook, then archive the cited row.
|
|
531
|
+
|
|
532
|
+
Same insert-first ordering as ``_replace_profile``: if insert
|
|
533
|
+
fails, the cited row stays current; if the post-insert archive
|
|
534
|
+
fails, log at ERROR and accept a transient duplicate rather
|
|
535
|
+
than losing data.
|
|
536
|
+
"""
|
|
537
|
+
storage = self.request_context.storage
|
|
538
|
+
if storage is None:
|
|
539
|
+
return False
|
|
540
|
+
owning_user_id = cited.user_id or request.user_id
|
|
541
|
+
new_playbook = UserPlaybook(
|
|
542
|
+
user_playbook_id=0, # auto-assigned by storage
|
|
543
|
+
user_id=owning_user_id,
|
|
544
|
+
agent_version=cited.agent_version or request.agent_version,
|
|
545
|
+
request_id=cited.request_id,
|
|
546
|
+
playbook_name=cited.playbook_name,
|
|
547
|
+
content=decision.new_content or cited.content,
|
|
548
|
+
trigger=(
|
|
549
|
+
decision.new_trigger
|
|
550
|
+
if decision.new_trigger is not None
|
|
551
|
+
else cited.trigger
|
|
552
|
+
),
|
|
553
|
+
rationale=(
|
|
554
|
+
decision.new_rationale
|
|
555
|
+
if decision.new_rationale is not None
|
|
556
|
+
else cited.rationale
|
|
557
|
+
),
|
|
558
|
+
status=None,
|
|
559
|
+
source=cited.source,
|
|
560
|
+
source_interaction_ids=list(cited.source_interaction_ids),
|
|
561
|
+
)
|
|
562
|
+
_log_edit_magnitude(
|
|
563
|
+
kind="playbook",
|
|
564
|
+
target_id=str(cited.user_playbook_id),
|
|
565
|
+
old_content=cited.content,
|
|
566
|
+
new_content=new_playbook.content,
|
|
567
|
+
)
|
|
568
|
+
# Flip is LLM-reported, not derived: when the model changes a rule's
|
|
569
|
+
# orientation it rewrites ``new_content`` and names the motivating
|
|
570
|
+
# failure/observation in ``new_rationale`` (per the memory_reflection
|
|
571
|
+
# prompt). A rewrite-with-rationale is the LLM-reported flip/revision
|
|
572
|
+
# signal — log it for observability without re-deriving polarity.
|
|
573
|
+
if decision.new_content is not None and decision.new_rationale:
|
|
574
|
+
logger.info(
|
|
575
|
+
"reflection.content_revision playbook_id=%s "
|
|
576
|
+
'content_excerpt="%s" prior_excerpt="%s" rationale_excerpt="%s"',
|
|
577
|
+
cited.user_playbook_id,
|
|
578
|
+
(new_playbook.content or "")[:120].replace('"', "'"),
|
|
579
|
+
(cited.content or "")[:120].replace('"', "'"),
|
|
580
|
+
(decision.new_rationale or "")[:120].replace('"', "'"),
|
|
581
|
+
)
|
|
582
|
+
storage.save_user_playbooks([new_playbook])
|
|
583
|
+
try:
|
|
584
|
+
archived = storage.archive_user_playbook_by_id(
|
|
585
|
+
user_id=owning_user_id,
|
|
586
|
+
user_playbook_id=cited.user_playbook_id,
|
|
587
|
+
)
|
|
588
|
+
except Exception as exc: # noqa: BLE001
|
|
589
|
+
with sentry_tags(
|
|
590
|
+
subsystem="reflection",
|
|
591
|
+
op="archive_after_insert",
|
|
592
|
+
kind="playbook",
|
|
593
|
+
org_id=self.request_context.org_id,
|
|
594
|
+
user_id=owning_user_id,
|
|
595
|
+
cited_id=cited.user_playbook_id,
|
|
596
|
+
new_id=new_playbook.user_playbook_id,
|
|
597
|
+
error_type=type(exc).__name__,
|
|
598
|
+
):
|
|
599
|
+
logger.exception(
|
|
600
|
+
"event=reflection_archive_after_insert_failed kind=playbook "
|
|
601
|
+
"cited_id=%s new_id=%s",
|
|
602
|
+
cited.user_playbook_id,
|
|
603
|
+
new_playbook.user_playbook_id,
|
|
604
|
+
)
|
|
605
|
+
return True
|
|
606
|
+
if not archived:
|
|
607
|
+
with sentry_tags(
|
|
608
|
+
subsystem="reflection",
|
|
609
|
+
op="archive_after_insert_noop",
|
|
610
|
+
kind="playbook",
|
|
611
|
+
org_id=self.request_context.org_id,
|
|
612
|
+
user_id=owning_user_id,
|
|
613
|
+
cited_id=cited.user_playbook_id,
|
|
614
|
+
new_id=new_playbook.user_playbook_id,
|
|
615
|
+
):
|
|
616
|
+
logger.error(
|
|
617
|
+
"event=reflection_archive_after_insert_noop kind=playbook "
|
|
618
|
+
"cited_id=%s new_id=%s",
|
|
619
|
+
cited.user_playbook_id,
|
|
620
|
+
new_playbook.user_playbook_id,
|
|
621
|
+
)
|
|
622
|
+
return True
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
_PROFILE_REVISION_FIELDS: tuple[str, ...] = (
|
|
626
|
+
"new_content",
|
|
627
|
+
"new_profile_time_to_live",
|
|
628
|
+
)
|
|
629
|
+
_PLAYBOOK_REVISION_FIELDS: tuple[str, ...] = (
|
|
630
|
+
"new_content",
|
|
631
|
+
"new_trigger",
|
|
632
|
+
"new_rationale",
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
_REVISION_FIELDS_BY_KIND: dict[str, tuple[str, ...]] = {
|
|
637
|
+
"profile": _PROFILE_REVISION_FIELDS,
|
|
638
|
+
"playbook": _PLAYBOOK_REVISION_FIELDS,
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _log_edit_magnitude(
|
|
643
|
+
*,
|
|
644
|
+
kind: str,
|
|
645
|
+
target_id: str,
|
|
646
|
+
old_content: str | None,
|
|
647
|
+
new_content: str | None,
|
|
648
|
+
) -> None:
|
|
649
|
+
"""Log a cheap edit-magnitude signal for one applied revision.
|
|
650
|
+
|
|
651
|
+
The magnitude is the content-size delta (new length minus old length)
|
|
652
|
+
in characters — a coarse proxy for how large the revision is, useful
|
|
653
|
+
for offline regularization analysis without storing diffs.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
kind (str): ``"profile"`` or ``"playbook"``.
|
|
657
|
+
target_id (str): Id of the cited row being replaced.
|
|
658
|
+
old_content (str | None): Cited row content before the revision.
|
|
659
|
+
new_content (str | None): Replacement row content after the revision.
|
|
660
|
+
"""
|
|
661
|
+
old_len = len(old_content or "")
|
|
662
|
+
new_len = len(new_content or "")
|
|
663
|
+
logger.info(
|
|
664
|
+
"event=reflection_edit_magnitude target_kind=%s target_id=%s "
|
|
665
|
+
"old_len=%d new_len=%d delta=%d",
|
|
666
|
+
kind,
|
|
667
|
+
target_id,
|
|
668
|
+
old_len,
|
|
669
|
+
new_len,
|
|
670
|
+
new_len - old_len,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _is_revision(decision: ReflectionDecision) -> bool:
|
|
675
|
+
"""Return True iff a revision field relevant to the target kind is set.
|
|
676
|
+
|
|
677
|
+
Splits the revision-field set by ``target_kind`` so a profile decision
|
|
678
|
+
is not classified as a revision purely because a playbook-only field
|
|
679
|
+
(e.g. ``new_trigger``) happens to be populated. This prevents
|
|
680
|
+
unnecessary replace/archive churn that would otherwise produce no
|
|
681
|
+
effective change.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
decision (ReflectionDecision): The decision to inspect.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
bool: True if at least one kind-relevant revision field is non-None.
|
|
688
|
+
"""
|
|
689
|
+
fields = _REVISION_FIELDS_BY_KIND.get(
|
|
690
|
+
decision.target_kind, _PLAYBOOK_REVISION_FIELDS
|
|
691
|
+
)
|
|
692
|
+
return any(getattr(decision, f) is not None for f in fields)
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _flatten(
|
|
696
|
+
request_models: list[RequestInteractionDataModel],
|
|
697
|
+
) -> list[Interaction]:
|
|
698
|
+
"""Flatten grouped request models into a flat list, oldest first."""
|
|
699
|
+
out: list[Interaction] = []
|
|
700
|
+
for rm in request_models:
|
|
701
|
+
out.extend(rm.interactions)
|
|
702
|
+
out.sort(key=lambda i: i.created_at)
|
|
703
|
+
return out
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _collect_citations(interactions: list[Interaction]) -> list[Citation]:
|
|
707
|
+
"""Pull distinct citations off Assistant interactions."""
|
|
708
|
+
seen: set[tuple[str, str]] = set()
|
|
709
|
+
out: list[Citation] = []
|
|
710
|
+
for i in interactions:
|
|
711
|
+
if i.role != "Assistant":
|
|
712
|
+
continue
|
|
713
|
+
for c in i.citations:
|
|
714
|
+
key = (c.kind, c.real_id)
|
|
715
|
+
if key in seen:
|
|
716
|
+
continue
|
|
717
|
+
seen.add(key)
|
|
718
|
+
out.append(c)
|
|
719
|
+
return out
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
@dataclass(frozen=True)
|
|
723
|
+
class _EligibleCitation:
|
|
724
|
+
"""A citation that passed the post-horizon filter.
|
|
725
|
+
|
|
726
|
+
Attributes:
|
|
727
|
+
citation (Citation): The citation itself.
|
|
728
|
+
position (int): 0-indexed position in the window where it
|
|
729
|
+
appeared (earliest occurrence — see deduplication note in
|
|
730
|
+
``_filter_citations_by_horizon``).
|
|
731
|
+
has_full_horizon (bool): True iff at least
|
|
732
|
+
``post_horizon_size`` interactions follow the citation in
|
|
733
|
+
the window. False indicates a ``last_chance`` judgment
|
|
734
|
+
with weaker evidence.
|
|
735
|
+
"""
|
|
736
|
+
|
|
737
|
+
citation: Citation
|
|
738
|
+
position: int
|
|
739
|
+
has_full_horizon: bool
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def _filter_citations_by_horizon(
|
|
743
|
+
citations: list[Citation],
|
|
744
|
+
window: list[Interaction],
|
|
745
|
+
post_horizon_size: int,
|
|
746
|
+
stride_size: int,
|
|
747
|
+
) -> list[_EligibleCitation]:
|
|
748
|
+
"""Filter citations to those that have enough post-citation context.
|
|
749
|
+
|
|
750
|
+
For each unique ``(kind, real_id)``, finds the **earliest** occurrence
|
|
751
|
+
in the window (maximizes follow-up turns) and decides:
|
|
752
|
+
|
|
753
|
+
- ``after_count >= post_horizon_size`` → eligible, ``has_full_horizon=True``.
|
|
754
|
+
- ``position < stride_size`` → eligible (last-chance, about to fall
|
|
755
|
+
out of the window next stride), ``has_full_horizon=False``.
|
|
756
|
+
- otherwise → deferred (excluded from this pass).
|
|
757
|
+
|
|
758
|
+
Only Assistant-role interactions contribute citations to consider —
|
|
759
|
+
user-role interactions are skipped even if their ``citations`` list is
|
|
760
|
+
populated.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
citations (list[Citation]): Distinct citations collected from
|
|
764
|
+
assistant turns in the window.
|
|
765
|
+
window (list[Interaction]): The reflection window, oldest first.
|
|
766
|
+
post_horizon_size (int): Minimum follow-up turns required for a
|
|
767
|
+
full-horizon judgment. Zero disables the horizon check.
|
|
768
|
+
stride_size (int): Used to detect citations about to fall out.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
list[_EligibleCitation]: Citations to send to the LLM, each
|
|
772
|
+
paired with its window position and horizon flag.
|
|
773
|
+
"""
|
|
774
|
+
seen: dict[tuple[str, str], int] = {}
|
|
775
|
+
for idx, interaction in enumerate(window):
|
|
776
|
+
if interaction.role != "Assistant":
|
|
777
|
+
continue
|
|
778
|
+
for c in interaction.citations:
|
|
779
|
+
key = (c.kind, c.real_id)
|
|
780
|
+
seen.setdefault(key, idx) # earliest occurrence only
|
|
781
|
+
|
|
782
|
+
citation_by_key = {(c.kind, c.real_id): c for c in citations}
|
|
783
|
+
|
|
784
|
+
out: list[_EligibleCitation] = []
|
|
785
|
+
for key, position in seen.items():
|
|
786
|
+
cite = citation_by_key.get(key)
|
|
787
|
+
if cite is None:
|
|
788
|
+
continue
|
|
789
|
+
after_count = len(window) - position - 1
|
|
790
|
+
if post_horizon_size <= 0 or after_count >= post_horizon_size:
|
|
791
|
+
out.append(
|
|
792
|
+
_EligibleCitation(
|
|
793
|
+
citation=cite, position=position, has_full_horizon=True
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
elif position < stride_size:
|
|
797
|
+
out.append(
|
|
798
|
+
_EligibleCitation(
|
|
799
|
+
citation=cite, position=position, has_full_horizon=False
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
# else: deferred — not included
|
|
803
|
+
return out
|