shellbrain 0.1.22__tar.gz → 0.1.24__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.
- {shellbrain-0.1.22 → shellbrain-0.1.24}/PKG-INFO +1 -1
- shellbrain-0.1.24/app/boot/migrations.py +115 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/telemetry.py +33 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/repos.py +5 -0
- shellbrain-0.1.24/app/core/use_cases/record_model_usage_telemetry.py +20 -0
- shellbrain-0.1.24/app/migrations/versions/20260414_0010_model_usage_telemetry.py +83 -0
- shellbrain-0.1.24/app/migrations/versions/20260414_0011_usage_problem_tokens_multi_solution_metrics.py +147 -0
- shellbrain-0.1.24/app/migrations/versions/20260415_0012_read_pack_cost_and_read_roi.py +58 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/doctor.py +13 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/init.py +5 -2
- shellbrain-0.1.24/app/periphery/admin/model_usage_backfill.py +123 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/handlers.py +23 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/main.py +30 -2
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/telemetry.py +53 -1
- shellbrain-0.1.24/app/periphery/db/models/views.py +662 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/telemetry_repo.py +18 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/claude_code.py +63 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/codex.py +52 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/cursor.py +79 -0
- shellbrain-0.1.24/app/periphery/episodes/model_usage.py +226 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/poller.py +23 -0
- shellbrain-0.1.24/app/periphery/identity/cursor_statusline.py +238 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/onboarding/host_assets.py +128 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/telemetry/operation_summary.py +47 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/pyproject.toml +1 -1
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/PKG-INFO +1 -1
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/SOURCES.txt +7 -0
- shellbrain-0.1.22/app/boot/migrations.py +0 -61
- shellbrain-0.1.22/app/periphery/db/models/views.py +0 -154
- {shellbrain-0.1.22 → shellbrain-0.1.24}/README.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/__main__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/_dsn_resolution.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/admin_db.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/config.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/create_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/db.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/embeddings.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/home.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/read_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/repos.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/thresholds.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/update_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/boot/use_cases.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/defaults/create_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/defaults/read_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/defaults/runtime.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/defaults/thresholds.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/defaults/update_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/config/loader.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/contracts/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/contracts/errors.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/contracts/requests.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/contracts/responses.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/associations.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/episodes.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/evidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/facts.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/identity.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/runtime_context.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/session_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/entities/utility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/clock.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/config.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/embeddings.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/idgen.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/session_state_store.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/interfaces/unit_of_work.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/_shared/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/_shared/executor.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/_shared/side_effects.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/create_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/create_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/bm25.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/context_pack_builder.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/expansion.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/fusion_rrf.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/lexical_query.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/scoring.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/read_policy/seed_retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/update_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/policies/update_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/build_guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/create_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/manage_session_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/read_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/record_episode_sync_telemetry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/record_operation_telemetry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/sync_episode.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/core/use_cases/update_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/env.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260226_0001_initial_schema.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260312_0002_add_hard_invariants.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260312_0003_drop_create_confidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260313_0004_episode_sync_hardening.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260313_0005_evidence_episode_event_refs.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260318_0006_usage_telemetry_schema.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260319_0007_identity_session_guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260320_0008_instance_metadata_and_backup_safety.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/20260410_0009_frontier_memory_family.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/migrations/versions/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/claude/CLAUDE.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/claude/skills/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/claude/skills/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/AGENTS.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/agents/openai.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain-large.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain-small.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain_logo.png +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/references/request-shapes.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-session-start/references/session-workflow.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-usage-review/agents/openai.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-usage-review/assets/shellbrain-small.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/codex/shellbrain-usage-review/assets/shellbrain_logo.png +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/cursor/skills/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/onboarding_assets/cursor/skills/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/agent_behavior_analysis.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/analytics.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/analytics_diagnostics.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/analytics_queries.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/backup.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/destructive_guard.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/external_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/init_errors.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/instance_guard.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/machine_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/managed_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/privileges.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/repo_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/restore.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/storage_setup.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/admin/upgrade.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/hydration.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/presenter_json.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/cli/schema_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/engine.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/associations.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/episodes.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/evidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/experiences.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/instance_metadata.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/memories.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/metadata.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/registry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/models/utility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/associations_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/episodes_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/evidence_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/experiences_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/memories_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/read_policy_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/relational/utility_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/semantic/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/semantic/keyword_retrieval_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/repos/semantic/semantic_retrieval_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/session.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/db/uow.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/embeddings/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/embeddings/local_provider.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/embeddings/query_vector_search.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/launcher.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/normalization.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/poller_lock.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/source_discovery.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/episodes/tool_filter.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/claude_hook_install.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/claude_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/codex_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/compatibility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/identity/resolver.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/artifacts.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/browser.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/queries.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/render_html.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/metrics/service.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/onboarding/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/session_state/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/session_state/file_store.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/telemetry/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/telemetry/session_selection.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/telemetry/sync_summary.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/validation/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/validation/integrity_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/app/periphery/validation/semantic_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/setup.cfg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/dependency_links.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/entry_points.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/requires.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.24}/shellbrain.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Packaged Alembic bootstrap helpers for installed-shellbrain database migrations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.resources import as_file, files
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
|
|
8
|
+
from alembic import command
|
|
9
|
+
from alembic.config import Config
|
|
10
|
+
from alembic.script import ScriptDirectory
|
|
11
|
+
from alembic.script.revision import ResolutionError
|
|
12
|
+
|
|
13
|
+
from app.boot.admin_db import get_admin_db_dsn, get_backup_dir, get_backup_mirror_dir, get_instance_mode_default
|
|
14
|
+
from app.boot.db import get_optional_db_dsn
|
|
15
|
+
from app.periphery.admin.destructive_guard import backup_and_verify_before_destructive_action
|
|
16
|
+
from app.periphery.admin.instance_guard import ensure_instance_metadata, fetch_instance_metadata
|
|
17
|
+
from app.periphery.admin.privileges import reconcile_app_role_privileges
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DatabaseRevisionAheadOfInstalledPackageError(RuntimeError):
|
|
21
|
+
"""Raised when the target database revision is newer than the installed package knows about."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def upgrade_database(revision: str = "head") -> None:
|
|
25
|
+
"""Apply packaged Alembic migrations to the configured database."""
|
|
26
|
+
|
|
27
|
+
config = Config()
|
|
28
|
+
admin_dsn = get_admin_db_dsn()
|
|
29
|
+
with as_file(files("app").joinpath("migrations")) as migrations_path:
|
|
30
|
+
config.set_main_option("script_location", str(migrations_path))
|
|
31
|
+
script = ScriptDirectory.from_config(config)
|
|
32
|
+
if _database_has_shellbrain_objects(admin_dsn):
|
|
33
|
+
_assert_database_revision_is_known(admin_dsn=admin_dsn, script=script)
|
|
34
|
+
backup_and_verify_before_destructive_action(
|
|
35
|
+
admin_dsn=admin_dsn,
|
|
36
|
+
backup_root=get_backup_dir(),
|
|
37
|
+
mirror_root=get_backup_mirror_dir(),
|
|
38
|
+
)
|
|
39
|
+
config.set_main_option("sqlalchemy.url", admin_dsn)
|
|
40
|
+
command.upgrade(config, revision)
|
|
41
|
+
if fetch_instance_metadata(admin_dsn) is None:
|
|
42
|
+
ensure_instance_metadata(
|
|
43
|
+
admin_dsn,
|
|
44
|
+
instance_mode=get_instance_mode_default(),
|
|
45
|
+
created_by="app.admin.migrate",
|
|
46
|
+
notes="Stamped by packaged migration runner.",
|
|
47
|
+
)
|
|
48
|
+
app_dsn = get_optional_db_dsn()
|
|
49
|
+
if app_dsn:
|
|
50
|
+
reconcile_app_role_privileges(admin_dsn=admin_dsn, app_dsn=app_dsn)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _database_has_shellbrain_objects(admin_dsn: str) -> bool:
|
|
54
|
+
"""Return whether the target database already contains Shellbrain-managed tables."""
|
|
55
|
+
|
|
56
|
+
import psycopg
|
|
57
|
+
|
|
58
|
+
with psycopg.connect(admin_dsn.replace("+psycopg", "")) as conn:
|
|
59
|
+
with conn.cursor() as cur:
|
|
60
|
+
cur.execute(
|
|
61
|
+
"""
|
|
62
|
+
SELECT EXISTS (
|
|
63
|
+
SELECT 1
|
|
64
|
+
FROM information_schema.tables
|
|
65
|
+
WHERE table_schema = 'public'
|
|
66
|
+
AND table_name IN ('memories', 'episodes', 'episode_events', 'operation_invocations')
|
|
67
|
+
)
|
|
68
|
+
"""
|
|
69
|
+
)
|
|
70
|
+
return bool(cur.fetchone()[0])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _assert_database_revision_is_known(*, admin_dsn: str, script: ScriptDirectory) -> None:
|
|
74
|
+
"""Fail early with a user-facing error when the database revision is newer than this package."""
|
|
75
|
+
|
|
76
|
+
current_revision = _fetch_database_revision(admin_dsn)
|
|
77
|
+
if current_revision is None:
|
|
78
|
+
return
|
|
79
|
+
try:
|
|
80
|
+
script.get_revision(current_revision)
|
|
81
|
+
except ResolutionError as exc:
|
|
82
|
+
installed_version = _installed_shellbrain_version()
|
|
83
|
+
raise DatabaseRevisionAheadOfInstalledPackageError(
|
|
84
|
+
"Installed Shellbrain package "
|
|
85
|
+
f"({installed_version}) cannot manage database revision {current_revision}. "
|
|
86
|
+
"This database was likely migrated by a newer Shellbrain release than the one currently installed. "
|
|
87
|
+
"Upgrade Shellbrain to a build that includes this revision, then rerun `shellbrain init` or "
|
|
88
|
+
"`shellbrain admin migrate`."
|
|
89
|
+
) from exc
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _fetch_database_revision(admin_dsn: str) -> str | None:
|
|
93
|
+
"""Return the current alembic revision when present."""
|
|
94
|
+
|
|
95
|
+
import psycopg
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
with psycopg.connect(admin_dsn.replace("+psycopg", "")) as conn:
|
|
99
|
+
with conn.cursor() as cur:
|
|
100
|
+
cur.execute("SELECT version_num FROM alembic_version")
|
|
101
|
+
row = cur.fetchone()
|
|
102
|
+
except psycopg.Error:
|
|
103
|
+
return None
|
|
104
|
+
if row is None or row[0] is None:
|
|
105
|
+
return None
|
|
106
|
+
return str(row[0])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _installed_shellbrain_version() -> str:
|
|
110
|
+
"""Return the installed Shellbrain package version when available."""
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
return importlib.metadata.version("shellbrain")
|
|
114
|
+
except importlib.metadata.PackageNotFoundError:
|
|
115
|
+
return "dev"
|
|
@@ -66,6 +66,12 @@ class ReadSummaryRecord:
|
|
|
66
66
|
implicit_related_count: int
|
|
67
67
|
total_returned: int
|
|
68
68
|
zero_results: bool
|
|
69
|
+
pack_char_count: int | None
|
|
70
|
+
pack_token_estimate: int | None
|
|
71
|
+
pack_token_estimate_method: str | None
|
|
72
|
+
direct_token_estimate: int | None
|
|
73
|
+
explicit_related_token_estimate: int | None
|
|
74
|
+
implicit_related_token_estimate: int | None
|
|
69
75
|
created_at: datetime
|
|
70
76
|
|
|
71
77
|
|
|
@@ -150,3 +156,30 @@ class EpisodeSyncToolTypeRecord:
|
|
|
150
156
|
sync_run_id: str
|
|
151
157
|
tool_type: str
|
|
152
158
|
event_count: int
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class ModelUsageRecord:
|
|
163
|
+
"""One normalized host model-usage event tied to a repo session."""
|
|
164
|
+
|
|
165
|
+
id: str
|
|
166
|
+
repo_id: str
|
|
167
|
+
thread_id: str | None
|
|
168
|
+
episode_id: str | None
|
|
169
|
+
host_app: str
|
|
170
|
+
host_session_key: str
|
|
171
|
+
host_usage_key: str
|
|
172
|
+
source_kind: str
|
|
173
|
+
occurred_at: datetime
|
|
174
|
+
agent_role: str
|
|
175
|
+
provider: str | None
|
|
176
|
+
model_id: str | None
|
|
177
|
+
input_tokens: int | None
|
|
178
|
+
output_tokens: int | None
|
|
179
|
+
reasoning_output_tokens: int | None
|
|
180
|
+
cached_input_tokens_total: int | None
|
|
181
|
+
cache_read_input_tokens: int | None
|
|
182
|
+
cache_creation_input_tokens: int | None
|
|
183
|
+
capture_quality: str
|
|
184
|
+
raw_usage_json: dict[str, Any] = field(default_factory=dict)
|
|
185
|
+
created_at: datetime | None = None
|
|
@@ -12,6 +12,7 @@ from app.core.entities.memory import Memory
|
|
|
12
12
|
from app.core.entities.telemetry import (
|
|
13
13
|
EpisodeSyncRunRecord,
|
|
14
14
|
EpisodeSyncToolTypeRecord,
|
|
15
|
+
ModelUsageRecord,
|
|
15
16
|
OperationInvocationRecord,
|
|
16
17
|
ReadResultItemRecord,
|
|
17
18
|
ReadSummaryRecord,
|
|
@@ -275,6 +276,10 @@ class ITelemetryRepo(ABC):
|
|
|
275
276
|
) -> None:
|
|
276
277
|
"""This method appends one sync-run row and its per-tool aggregates."""
|
|
277
278
|
|
|
279
|
+
@abstractmethod
|
|
280
|
+
def insert_model_usage(self, records: Sequence[ModelUsageRecord]) -> None:
|
|
281
|
+
"""This method appends normalized model-usage rows idempotently."""
|
|
282
|
+
|
|
278
283
|
@abstractmethod
|
|
279
284
|
def update_operation_polling(self, invocation_id: str, *, attempted: bool, started: bool) -> None:
|
|
280
285
|
"""This method patches the poller-start flags for one existing invocation row."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Thin orchestration for normalized model-usage telemetry writes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from app.core.entities.telemetry import ModelUsageRecord
|
|
8
|
+
from app.core.interfaces.unit_of_work import IUnitOfWork
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def record_model_usage_telemetry(
|
|
12
|
+
*,
|
|
13
|
+
uow: IUnitOfWork,
|
|
14
|
+
records: Sequence[ModelUsageRecord],
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Persist normalized model-usage rows without affecting callers."""
|
|
17
|
+
|
|
18
|
+
if not records:
|
|
19
|
+
return
|
|
20
|
+
uow.telemetry.insert_model_usage(tuple(records))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Add model-usage telemetry storage and derived views."""
|
|
2
|
+
|
|
3
|
+
from alembic import op
|
|
4
|
+
|
|
5
|
+
from app.periphery.db.models.views import (
|
|
6
|
+
USAGE_COMMAND_DAILY_SQL,
|
|
7
|
+
USAGE_MEMORY_RETRIEVAL_SQL,
|
|
8
|
+
USAGE_PROBLEM_TOKENS_SQL,
|
|
9
|
+
USAGE_SESSION_PROTOCOL_SQL,
|
|
10
|
+
USAGE_SESSION_TOKENS_SQL,
|
|
11
|
+
USAGE_SYNC_HEALTH_SQL,
|
|
12
|
+
USAGE_TOKEN_CAPTURE_HEALTH_SQL,
|
|
13
|
+
USAGE_WRITE_EFFECTS_SQL,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
revision = "20260414_0010"
|
|
17
|
+
down_revision = "20260410_0009"
|
|
18
|
+
branch_labels = None
|
|
19
|
+
depends_on = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def upgrade() -> None:
|
|
23
|
+
"""Create model-usage telemetry storage and derived views."""
|
|
24
|
+
|
|
25
|
+
op.execute(
|
|
26
|
+
"""
|
|
27
|
+
CREATE TABLE model_usage (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
repo_id TEXT NOT NULL,
|
|
30
|
+
thread_id TEXT,
|
|
31
|
+
episode_id TEXT,
|
|
32
|
+
host_app TEXT NOT NULL,
|
|
33
|
+
host_session_key TEXT NOT NULL,
|
|
34
|
+
host_usage_key TEXT NOT NULL,
|
|
35
|
+
source_kind TEXT NOT NULL,
|
|
36
|
+
occurred_at TIMESTAMPTZ NOT NULL,
|
|
37
|
+
agent_role TEXT NOT NULL DEFAULT 'foreground',
|
|
38
|
+
provider TEXT,
|
|
39
|
+
model_id TEXT,
|
|
40
|
+
input_tokens BIGINT,
|
|
41
|
+
output_tokens BIGINT,
|
|
42
|
+
reasoning_output_tokens BIGINT,
|
|
43
|
+
cached_input_tokens_total BIGINT,
|
|
44
|
+
cache_read_input_tokens BIGINT,
|
|
45
|
+
cache_creation_input_tokens BIGINT,
|
|
46
|
+
capture_quality TEXT NOT NULL DEFAULT 'exact',
|
|
47
|
+
raw_usage_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
48
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
49
|
+
CONSTRAINT uq_model_usage_host_session_usage UNIQUE (host_app, host_session_key, host_usage_key)
|
|
50
|
+
);
|
|
51
|
+
CREATE INDEX idx_model_usage_repo_thread_occurred_at
|
|
52
|
+
ON model_usage(repo_id, thread_id, occurred_at);
|
|
53
|
+
CREATE INDEX idx_model_usage_repo_host_session_occurred_at
|
|
54
|
+
ON model_usage(repo_id, host_app, host_session_key, occurred_at);
|
|
55
|
+
"""
|
|
56
|
+
)
|
|
57
|
+
op.execute(USAGE_COMMAND_DAILY_SQL)
|
|
58
|
+
op.execute(USAGE_MEMORY_RETRIEVAL_SQL)
|
|
59
|
+
op.execute(USAGE_WRITE_EFFECTS_SQL)
|
|
60
|
+
op.execute(USAGE_SYNC_HEALTH_SQL)
|
|
61
|
+
op.execute(USAGE_SESSION_PROTOCOL_SQL)
|
|
62
|
+
op.execute(USAGE_SESSION_TOKENS_SQL)
|
|
63
|
+
op.execute(USAGE_PROBLEM_TOKENS_SQL)
|
|
64
|
+
op.execute(USAGE_TOKEN_CAPTURE_HEALTH_SQL)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def downgrade() -> None:
|
|
68
|
+
"""Drop model-usage telemetry views and table."""
|
|
69
|
+
|
|
70
|
+
op.execute(
|
|
71
|
+
"""
|
|
72
|
+
DROP VIEW IF EXISTS usage_token_capture_health;
|
|
73
|
+
DROP VIEW IF EXISTS usage_problem_tokens;
|
|
74
|
+
DROP VIEW IF EXISTS usage_session_tokens;
|
|
75
|
+
DROP VIEW IF EXISTS usage_session_protocol;
|
|
76
|
+
DROP VIEW IF EXISTS usage_sync_health;
|
|
77
|
+
DROP VIEW IF EXISTS usage_write_effects;
|
|
78
|
+
DROP VIEW IF EXISTS usage_memory_retrieval;
|
|
79
|
+
DROP VIEW IF EXISTS usage_command_daily;
|
|
80
|
+
|
|
81
|
+
DROP TABLE IF EXISTS model_usage;
|
|
82
|
+
"""
|
|
83
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Expand usage_problem_tokens with latest-solution metrics."""
|
|
2
|
+
|
|
3
|
+
from alembic import op
|
|
4
|
+
|
|
5
|
+
from app.periphery.db.models.views import USAGE_PROBLEM_TOKENS_SQL
|
|
6
|
+
|
|
7
|
+
revision = "20260414_0011"
|
|
8
|
+
down_revision = "20260414_0010"
|
|
9
|
+
branch_labels = None
|
|
10
|
+
depends_on = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
PREVIOUS_USAGE_PROBLEM_TOKENS_SQL = """
|
|
14
|
+
CREATE OR REPLACE VIEW usage_problem_tokens AS
|
|
15
|
+
WITH session_quality AS (
|
|
16
|
+
SELECT
|
|
17
|
+
repo_id,
|
|
18
|
+
host_app,
|
|
19
|
+
host_session_key,
|
|
20
|
+
BOOL_OR(capture_quality = 'exact') AS has_exact_rows,
|
|
21
|
+
BOOL_OR(
|
|
22
|
+
capture_quality = 'exact'
|
|
23
|
+
AND (
|
|
24
|
+
COALESCE(input_tokens, 0) > 0
|
|
25
|
+
OR COALESCE(output_tokens, 0) > 0
|
|
26
|
+
OR COALESCE(cached_input_tokens_total, 0) > 0
|
|
27
|
+
OR COALESCE(reasoning_output_tokens, 0) > 0
|
|
28
|
+
)
|
|
29
|
+
) AS has_nonzero_exact_rows,
|
|
30
|
+
BOOL_OR(capture_quality = 'estimated') AS has_estimated_rows
|
|
31
|
+
FROM model_usage
|
|
32
|
+
GROUP BY repo_id, host_app, host_session_key
|
|
33
|
+
),
|
|
34
|
+
preferred_rows AS (
|
|
35
|
+
SELECT mu.*
|
|
36
|
+
FROM model_usage mu
|
|
37
|
+
JOIN session_quality sq
|
|
38
|
+
ON sq.repo_id = mu.repo_id
|
|
39
|
+
AND sq.host_app = mu.host_app
|
|
40
|
+
AND sq.host_session_key = mu.host_session_key
|
|
41
|
+
WHERE (
|
|
42
|
+
sq.has_nonzero_exact_rows
|
|
43
|
+
AND mu.capture_quality = 'exact'
|
|
44
|
+
) OR (
|
|
45
|
+
NOT sq.has_nonzero_exact_rows
|
|
46
|
+
AND mu.capture_quality = 'estimated'
|
|
47
|
+
) OR (
|
|
48
|
+
NOT sq.has_nonzero_exact_rows
|
|
49
|
+
AND NOT sq.has_estimated_rows
|
|
50
|
+
AND sq.has_exact_rows
|
|
51
|
+
AND mu.capture_quality = 'exact'
|
|
52
|
+
)
|
|
53
|
+
),
|
|
54
|
+
problem_threads AS (
|
|
55
|
+
SELECT
|
|
56
|
+
p.id AS problem_id,
|
|
57
|
+
p.repo_id,
|
|
58
|
+
p.created_at AS problem_created_at,
|
|
59
|
+
(
|
|
60
|
+
ARRAY_AGG(ep.thread_id ORDER BY ee.created_at ASC, ee.seq ASC)
|
|
61
|
+
FILTER (WHERE ep.thread_id IS NOT NULL)
|
|
62
|
+
)[1] AS thread_id
|
|
63
|
+
FROM memories p
|
|
64
|
+
JOIN memory_evidence me ON me.memory_id = p.id
|
|
65
|
+
JOIN evidence_refs er ON er.id = me.evidence_id
|
|
66
|
+
JOIN episode_events ee ON ee.id = COALESCE(er.episode_event_id, er.ref)
|
|
67
|
+
JOIN episodes ep ON ep.id = ee.episode_id
|
|
68
|
+
WHERE p.kind = 'problem'
|
|
69
|
+
GROUP BY p.id, p.repo_id, p.created_at
|
|
70
|
+
),
|
|
71
|
+
first_solutions AS (
|
|
72
|
+
SELECT
|
|
73
|
+
pa.problem_id,
|
|
74
|
+
s.id AS solution_id,
|
|
75
|
+
s.created_at AS solution_created_at,
|
|
76
|
+
ROW_NUMBER() OVER (
|
|
77
|
+
PARTITION BY pa.problem_id
|
|
78
|
+
ORDER BY s.created_at ASC, s.id ASC
|
|
79
|
+
) AS row_num
|
|
80
|
+
FROM problem_attempts pa
|
|
81
|
+
JOIN memories s ON s.id = pa.attempt_id
|
|
82
|
+
WHERE pa.role = 'solution'
|
|
83
|
+
AND s.kind = 'solution'
|
|
84
|
+
),
|
|
85
|
+
problem_windows AS (
|
|
86
|
+
SELECT
|
|
87
|
+
pt.repo_id,
|
|
88
|
+
pt.problem_id,
|
|
89
|
+
pt.thread_id,
|
|
90
|
+
pt.problem_created_at,
|
|
91
|
+
fs.solution_id,
|
|
92
|
+
fs.solution_created_at
|
|
93
|
+
FROM problem_threads pt
|
|
94
|
+
JOIN first_solutions fs
|
|
95
|
+
ON fs.problem_id = pt.problem_id
|
|
96
|
+
AND fs.row_num = 1
|
|
97
|
+
WHERE pt.thread_id IS NOT NULL
|
|
98
|
+
)
|
|
99
|
+
SELECT
|
|
100
|
+
pw.repo_id,
|
|
101
|
+
pw.problem_id,
|
|
102
|
+
pw.solution_id,
|
|
103
|
+
pw.thread_id,
|
|
104
|
+
pw.problem_created_at,
|
|
105
|
+
pw.solution_created_at,
|
|
106
|
+
COUNT(pr.id)::INTEGER AS usage_row_count,
|
|
107
|
+
COALESCE(SUM(pr.input_tokens), 0)::BIGINT AS input_tokens,
|
|
108
|
+
COALESCE(SUM(pr.output_tokens), 0)::BIGINT AS output_tokens,
|
|
109
|
+
COALESCE(SUM(pr.reasoning_output_tokens), 0)::BIGINT AS reasoning_output_tokens,
|
|
110
|
+
COALESCE(SUM(pr.cached_input_tokens_total), 0)::BIGINT AS cached_input_tokens_total,
|
|
111
|
+
COALESCE(SUM(pr.cache_read_input_tokens), 0)::BIGINT AS cache_read_input_tokens,
|
|
112
|
+
COALESCE(SUM(pr.cache_creation_input_tokens), 0)::BIGINT AS cache_creation_input_tokens,
|
|
113
|
+
(
|
|
114
|
+
COALESCE(SUM(pr.input_tokens), 0)
|
|
115
|
+
+ COALESCE(SUM(pr.output_tokens), 0)
|
|
116
|
+
)::BIGINT AS fresh_work_tokens,
|
|
117
|
+
(
|
|
118
|
+
COALESCE(SUM(pr.input_tokens), 0)
|
|
119
|
+
+ COALESCE(SUM(pr.cached_input_tokens_total), 0)
|
|
120
|
+
+ COALESCE(SUM(pr.output_tokens), 0)
|
|
121
|
+
)::BIGINT AS all_tokens_including_cache
|
|
122
|
+
FROM problem_windows pw
|
|
123
|
+
LEFT JOIN preferred_rows pr
|
|
124
|
+
ON pr.repo_id = pw.repo_id
|
|
125
|
+
AND pr.thread_id = pw.thread_id
|
|
126
|
+
AND pr.occurred_at >= pw.problem_created_at
|
|
127
|
+
AND pr.occurred_at <= pw.solution_created_at
|
|
128
|
+
GROUP BY
|
|
129
|
+
pw.repo_id,
|
|
130
|
+
pw.problem_id,
|
|
131
|
+
pw.solution_id,
|
|
132
|
+
pw.thread_id,
|
|
133
|
+
pw.problem_created_at,
|
|
134
|
+
pw.solution_created_at;
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def upgrade() -> None:
|
|
139
|
+
"""Replace usage_problem_tokens with first- and latest-solution metrics."""
|
|
140
|
+
|
|
141
|
+
op.execute(USAGE_PROBLEM_TOKENS_SQL)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def downgrade() -> None:
|
|
145
|
+
"""Restore the prior usage_problem_tokens definition."""
|
|
146
|
+
|
|
147
|
+
op.execute(PREVIOUS_USAGE_PROBLEM_TOKENS_SQL)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Add read-pack cost telemetry and read-before-solve ROI views."""
|
|
2
|
+
|
|
3
|
+
from alembic import op
|
|
4
|
+
|
|
5
|
+
from app.periphery.db.models.views import (
|
|
6
|
+
USAGE_PROBLEM_READ_ROI_SQL,
|
|
7
|
+
USAGE_READ_BEFORE_SOLVE_ROI_SQL,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
revision = "20260415_0012"
|
|
11
|
+
down_revision = "20260414_0011"
|
|
12
|
+
branch_labels = None
|
|
13
|
+
depends_on = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def upgrade() -> None:
|
|
17
|
+
"""Add read-pack estimate columns, a read-focused partial index, and ROI views."""
|
|
18
|
+
|
|
19
|
+
op.execute(
|
|
20
|
+
"""
|
|
21
|
+
ALTER TABLE read_invocation_summaries
|
|
22
|
+
ADD COLUMN pack_char_count INTEGER,
|
|
23
|
+
ADD COLUMN pack_token_estimate INTEGER,
|
|
24
|
+
ADD COLUMN pack_token_estimate_method TEXT,
|
|
25
|
+
ADD COLUMN direct_token_estimate INTEGER,
|
|
26
|
+
ADD COLUMN explicit_related_token_estimate INTEGER,
|
|
27
|
+
ADD COLUMN implicit_related_token_estimate INTEGER;
|
|
28
|
+
|
|
29
|
+
CREATE INDEX idx_operation_invocations_successful_read_thread_created_at
|
|
30
|
+
ON operation_invocations(repo_id, selected_thread_id, created_at)
|
|
31
|
+
WHERE command = 'read'
|
|
32
|
+
AND outcome = 'ok'
|
|
33
|
+
AND selected_thread_id IS NOT NULL;
|
|
34
|
+
"""
|
|
35
|
+
)
|
|
36
|
+
op.execute(USAGE_PROBLEM_READ_ROI_SQL)
|
|
37
|
+
op.execute(USAGE_READ_BEFORE_SOLVE_ROI_SQL)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def downgrade() -> None:
|
|
41
|
+
"""Drop read ROI views, partial index, and read-pack estimate columns."""
|
|
42
|
+
|
|
43
|
+
op.execute(
|
|
44
|
+
"""
|
|
45
|
+
DROP VIEW IF EXISTS usage_read_before_solve_roi;
|
|
46
|
+
DROP VIEW IF EXISTS usage_problem_read_roi;
|
|
47
|
+
|
|
48
|
+
DROP INDEX IF EXISTS idx_operation_invocations_successful_read_thread_created_at;
|
|
49
|
+
|
|
50
|
+
ALTER TABLE read_invocation_summaries
|
|
51
|
+
DROP COLUMN IF EXISTS implicit_related_token_estimate,
|
|
52
|
+
DROP COLUMN IF EXISTS explicit_related_token_estimate,
|
|
53
|
+
DROP COLUMN IF EXISTS direct_token_estimate,
|
|
54
|
+
DROP COLUMN IF EXISTS pack_token_estimate_method,
|
|
55
|
+
DROP COLUMN IF EXISTS pack_token_estimate,
|
|
56
|
+
DROP COLUMN IF EXISTS pack_char_count;
|
|
57
|
+
"""
|
|
58
|
+
)
|
|
@@ -54,6 +54,18 @@ def build_doctor_report(
|
|
|
54
54
|
disk = shutil.disk_usage(home_root if home_root.exists() else home_root.parent)
|
|
55
55
|
repo_report = _build_repo_report(repo_root=repo_root)
|
|
56
56
|
host_integrations = inspect_host_assets()
|
|
57
|
+
cursor_statusline = getattr(
|
|
58
|
+
host_integrations,
|
|
59
|
+
"cursor_statusline",
|
|
60
|
+
{
|
|
61
|
+
"installed": False,
|
|
62
|
+
"managed": False,
|
|
63
|
+
"malformed": False,
|
|
64
|
+
"path": None,
|
|
65
|
+
"command_executable": None,
|
|
66
|
+
"executable_exists": None,
|
|
67
|
+
},
|
|
68
|
+
)
|
|
57
69
|
runtime_warnings = _runtime_warnings(machine_config)
|
|
58
70
|
|
|
59
71
|
report: dict[str, Any] = {
|
|
@@ -88,6 +100,7 @@ def build_doctor_report(
|
|
|
88
100
|
"claude_startup_guidance": host_integrations.claude_startup_guidance,
|
|
89
101
|
"claude_skill": host_integrations.claude_skill,
|
|
90
102
|
"cursor_skill": host_integrations.cursor_skill,
|
|
103
|
+
"cursor_statusline": cursor_statusline,
|
|
91
104
|
"claude_global_hook": host_integrations.claude_global_hook,
|
|
92
105
|
},
|
|
93
106
|
"repo": repo_report,
|
|
@@ -469,10 +469,13 @@ def _reconcile_database(config: MachineConfig) -> tuple[bool, MachineConfig]:
|
|
|
469
469
|
def _apply_schema_migrations(config: MachineConfig) -> bool:
|
|
470
470
|
"""Apply packaged schema migrations to the configured Shellbrain database."""
|
|
471
471
|
|
|
472
|
-
from app.boot.migrations import upgrade_database
|
|
472
|
+
from app.boot.migrations import DatabaseRevisionAheadOfInstalledPackageError, upgrade_database
|
|
473
473
|
|
|
474
474
|
before_revision = _fetch_schema_revision(config.database.admin_dsn)
|
|
475
|
-
|
|
475
|
+
try:
|
|
476
|
+
upgrade_database()
|
|
477
|
+
except DatabaseRevisionAheadOfInstalledPackageError as exc:
|
|
478
|
+
raise InitConflictError(str(exc)) from exc
|
|
476
479
|
after_revision = _fetch_schema_revision(config.database.admin_dsn)
|
|
477
480
|
return before_revision != after_revision
|
|
478
481
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Retroactive token-usage backfill from Shellbrain-linked host transcripts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import text
|
|
10
|
+
from sqlalchemy.engine import Engine
|
|
11
|
+
|
|
12
|
+
from app.boot.use_cases import get_uow_factory
|
|
13
|
+
from app.core.entities.telemetry import ModelUsageRecord
|
|
14
|
+
from app.core.use_cases.record_model_usage_telemetry import record_model_usage_telemetry
|
|
15
|
+
from app.periphery.episodes.model_usage import collect_model_usage_records_for_session
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class BackfillSummary:
|
|
20
|
+
"""Small structured summary for token-usage backfill runs."""
|
|
21
|
+
|
|
22
|
+
sessions_examined: int
|
|
23
|
+
sessions_with_records: int
|
|
24
|
+
sessions_skipped: int
|
|
25
|
+
sessions_failed: int
|
|
26
|
+
records_attempted: int
|
|
27
|
+
host_counts: dict[str, int]
|
|
28
|
+
errors: list[dict[str, str]]
|
|
29
|
+
|
|
30
|
+
def to_payload(self) -> dict[str, object]:
|
|
31
|
+
"""Render the summary into JSON-safe primitives."""
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
"sessions_examined": self.sessions_examined,
|
|
35
|
+
"sessions_with_records": self.sessions_with_records,
|
|
36
|
+
"sessions_skipped": self.sessions_skipped,
|
|
37
|
+
"sessions_failed": self.sessions_failed,
|
|
38
|
+
"records_attempted": self.records_attempted,
|
|
39
|
+
"host_counts": self.host_counts,
|
|
40
|
+
"errors": self.errors,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def backfill_model_usage(*, engine: Engine) -> BackfillSummary:
|
|
45
|
+
"""Backfill normalized model usage for all Shellbrain-linked historical sessions."""
|
|
46
|
+
|
|
47
|
+
rows = _load_linked_sessions(engine=engine)
|
|
48
|
+
host_counts: Counter[str] = Counter()
|
|
49
|
+
errors: list[dict[str, str]] = []
|
|
50
|
+
sessions_with_records = 0
|
|
51
|
+
sessions_skipped = 0
|
|
52
|
+
sessions_failed = 0
|
|
53
|
+
records_attempted = 0
|
|
54
|
+
uow_factory = get_uow_factory()
|
|
55
|
+
|
|
56
|
+
for row in rows:
|
|
57
|
+
transcript_path = Path(str(row["transcript_path"]))
|
|
58
|
+
try:
|
|
59
|
+
records = collect_model_usage_records_for_session(
|
|
60
|
+
repo_id=str(row["repo_id"]),
|
|
61
|
+
host_app=str(row["host_app"]),
|
|
62
|
+
host_session_key=str(row["host_session_key"]),
|
|
63
|
+
thread_id=str(row["thread_id"]) if row["thread_id"] is not None else None,
|
|
64
|
+
episode_id=str(row["episode_id"]) if row["episode_id"] is not None else None,
|
|
65
|
+
transcript_path=transcript_path,
|
|
66
|
+
)
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
sessions_failed += 1
|
|
69
|
+
errors.append(
|
|
70
|
+
{
|
|
71
|
+
"host_app": str(row["host_app"]),
|
|
72
|
+
"host_session_key": str(row["host_session_key"]),
|
|
73
|
+
"message": str(exc),
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if not records:
|
|
79
|
+
sessions_skipped += 1
|
|
80
|
+
continue
|
|
81
|
+
_persist_records(uow_factory=uow_factory, records=records)
|
|
82
|
+
sessions_with_records += 1
|
|
83
|
+
records_attempted += len(records)
|
|
84
|
+
host_counts[str(row["host_app"])] += len(records)
|
|
85
|
+
|
|
86
|
+
return BackfillSummary(
|
|
87
|
+
sessions_examined=len(rows),
|
|
88
|
+
sessions_with_records=sessions_with_records,
|
|
89
|
+
sessions_skipped=sessions_skipped,
|
|
90
|
+
sessions_failed=sessions_failed,
|
|
91
|
+
records_attempted=records_attempted,
|
|
92
|
+
host_counts=dict(sorted(host_counts.items())),
|
|
93
|
+
errors=errors,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _persist_records(*, uow_factory, records: list[ModelUsageRecord]) -> None:
|
|
98
|
+
"""Persist a batch of model-usage rows in one transaction."""
|
|
99
|
+
|
|
100
|
+
with uow_factory() as uow:
|
|
101
|
+
record_model_usage_telemetry(uow=uow, records=tuple(records))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_linked_sessions(*, engine: Engine) -> list[dict[str, object]]:
|
|
105
|
+
"""Return the latest Shellbrain-linked sync record per repo/host/session."""
|
|
106
|
+
|
|
107
|
+
statement = text(
|
|
108
|
+
"""
|
|
109
|
+
SELECT DISTINCT ON (repo_id, host_app, host_session_key)
|
|
110
|
+
repo_id,
|
|
111
|
+
host_app,
|
|
112
|
+
host_session_key,
|
|
113
|
+
thread_id,
|
|
114
|
+
episode_id,
|
|
115
|
+
transcript_path
|
|
116
|
+
FROM episode_sync_runs
|
|
117
|
+
WHERE episode_id IS NOT NULL
|
|
118
|
+
AND transcript_path IS NOT NULL
|
|
119
|
+
ORDER BY repo_id, host_app, host_session_key, created_at DESC, id DESC
|
|
120
|
+
"""
|
|
121
|
+
)
|
|
122
|
+
with engine.connect() as conn:
|
|
123
|
+
return [dict(row) for row in conn.execute(statement).mappings().all()]
|