shellbrain 0.1.22__tar.gz → 0.1.23__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.23}/PKG-INFO +1 -1
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/telemetry.py +33 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/repos.py +5 -0
- shellbrain-0.1.23/app/core/use_cases/record_model_usage_telemetry.py +20 -0
- shellbrain-0.1.23/app/migrations/versions/20260414_0010_model_usage_telemetry.py +83 -0
- shellbrain-0.1.23/app/migrations/versions/20260414_0011_usage_problem_tokens_multi_solution_metrics.py +147 -0
- shellbrain-0.1.23/app/migrations/versions/20260415_0012_read_pack_cost_and_read_roi.py +58 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/doctor.py +13 -0
- shellbrain-0.1.23/app/periphery/admin/model_usage_backfill.py +123 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/handlers.py +23 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/main.py +24 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/telemetry.py +53 -1
- shellbrain-0.1.23/app/periphery/db/models/views.py +662 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/telemetry_repo.py +18 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/claude_code.py +63 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/codex.py +52 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/cursor.py +79 -0
- shellbrain-0.1.23/app/periphery/episodes/model_usage.py +226 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/poller.py +23 -0
- shellbrain-0.1.23/app/periphery/identity/cursor_statusline.py +238 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/onboarding/host_assets.py +128 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/telemetry/operation_summary.py +47 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/pyproject.toml +1 -1
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/PKG-INFO +1 -1
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/SOURCES.txt +7 -0
- shellbrain-0.1.22/app/periphery/db/models/views.py +0 -154
- {shellbrain-0.1.22 → shellbrain-0.1.23}/README.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/__main__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/_dsn_resolution.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/admin_db.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/config.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/create_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/db.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/embeddings.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/home.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/migrations.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/read_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/repos.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/thresholds.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/update_policy.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/boot/use_cases.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/defaults/create_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/defaults/read_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/defaults/runtime.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/defaults/thresholds.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/defaults/update_policy.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/config/loader.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/contracts/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/contracts/errors.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/contracts/requests.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/contracts/responses.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/associations.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/episodes.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/evidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/facts.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/identity.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/runtime_context.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/session_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/entities/utility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/clock.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/config.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/embeddings.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/idgen.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/session_state_store.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/interfaces/unit_of_work.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/_shared/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/_shared/executor.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/_shared/side_effects.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/create_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/create_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/bm25.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/context_pack_builder.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/expansion.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/fusion_rrf.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/lexical_query.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/scoring.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/read_policy/seed_retrieval.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/update_policy/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/policies/update_policy/pipeline.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/build_guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/create_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/manage_session_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/read_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/record_episode_sync_telemetry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/record_operation_telemetry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/sync_episode.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/core/use_cases/update_memory.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/env.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260226_0001_initial_schema.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260312_0002_add_hard_invariants.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260312_0003_drop_create_confidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260313_0004_episode_sync_hardening.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260313_0005_evidence_episode_event_refs.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260318_0006_usage_telemetry_schema.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260319_0007_identity_session_guidance.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260320_0008_instance_metadata_and_backup_safety.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/20260410_0009_frontier_memory_family.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/migrations/versions/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/claude/CLAUDE.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/claude/skills/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/claude/skills/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/AGENTS.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/agents/openai.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain-large.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain-small.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/assets/shellbrain_logo.png +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/references/request-shapes.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-session-start/references/session-workflow.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-usage-review/agents/openai.yaml +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-usage-review/assets/shellbrain-small.svg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/codex/shellbrain-usage-review/assets/shellbrain_logo.png +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/cursor/skills/shellbrain-session-start/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/onboarding_assets/cursor/skills/shellbrain-usage-review/SKILL.md +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/agent_behavior_analysis.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/analytics.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/analytics_diagnostics.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/analytics_queries.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/backup.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/destructive_guard.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/external_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/init.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/init_errors.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/instance_guard.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/machine_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/managed_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/privileges.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/repo_state.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/restore.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/storage_setup.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/admin/upgrade.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/hydration.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/presenter_json.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/cli/schema_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/engine.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/associations.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/episodes.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/evidence.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/experiences.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/instance_metadata.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/memories.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/metadata.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/registry.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/models/utility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/associations_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/episodes_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/evidence_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/experiences_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/memories_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/read_policy_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/relational/utility_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/semantic/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/semantic/keyword_retrieval_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/repos/semantic/semantic_retrieval_repo.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/session.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/db/uow.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/embeddings/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/embeddings/local_provider.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/embeddings/query_vector_search.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/launcher.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/normalization.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/poller_lock.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/source_discovery.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/episodes/tool_filter.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/claude_hook_install.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/claude_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/codex_runtime.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/compatibility.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/identity/resolver.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/artifacts.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/browser.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/queries.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/render_html.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/metrics/service.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/onboarding/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/session_state/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/session_state/file_store.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/telemetry/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/telemetry/session_selection.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/telemetry/sync_summary.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/validation/__init__.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/validation/integrity_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/app/periphery/validation/semantic_validation.py +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/setup.cfg +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/dependency_links.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/entry_points.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/requires.txt +0 -0
- {shellbrain-0.1.22 → shellbrain-0.1.23}/shellbrain.egg-info/top_level.txt +0 -0
|
@@ -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,
|
|
@@ -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()]
|
|
@@ -26,6 +26,7 @@ from app.core.use_cases.manage_session_state import SessionStateManager
|
|
|
26
26
|
from app.core.use_cases.create_memory import execute_create_memory
|
|
27
27
|
from app.core.use_cases.read_memory import execute_read_memory
|
|
28
28
|
from app.core.use_cases.record_episode_sync_telemetry import record_episode_sync_telemetry
|
|
29
|
+
from app.core.use_cases.record_model_usage_telemetry import record_model_usage_telemetry
|
|
29
30
|
from app.core.use_cases.record_operation_telemetry import record_operation_telemetry
|
|
30
31
|
from app.core.use_cases.sync_episode import sync_episode
|
|
31
32
|
from app.core.use_cases.update_memory import execute_update_memory
|
|
@@ -46,6 +47,7 @@ from app.periphery.cli.schema_validation import (
|
|
|
46
47
|
validate_update_schema,
|
|
47
48
|
)
|
|
48
49
|
from app.periphery.episodes.normalization import normalize_host_transcript
|
|
50
|
+
from app.periphery.episodes.model_usage import collect_model_usage_records_for_session
|
|
49
51
|
from app.periphery.identity.resolver import (
|
|
50
52
|
discover_untrusted_events_candidate,
|
|
51
53
|
resolve_caller_identity,
|
|
@@ -305,6 +307,7 @@ def handle_events(
|
|
|
305
307
|
selection_summary = SessionSelectionSummary()
|
|
306
308
|
sync_run = None
|
|
307
309
|
sync_tool_types = ()
|
|
310
|
+
model_usage_records = ()
|
|
308
311
|
try:
|
|
309
312
|
agent_request, errors = validate_events_schema(payload)
|
|
310
313
|
if errors:
|
|
@@ -392,6 +395,19 @@ def handle_events(
|
|
|
392
395
|
system_event_count=int(sync_result["system_event_count"]),
|
|
393
396
|
tool_type_counts=dict(sync_result["tool_type_counts"]),
|
|
394
397
|
)
|
|
398
|
+
try:
|
|
399
|
+
model_usage_records = tuple(
|
|
400
|
+
collect_model_usage_records_for_session(
|
|
401
|
+
repo_id=request.repo_id,
|
|
402
|
+
host_app=str(source.host_app),
|
|
403
|
+
host_session_key=str(source.host_session_key),
|
|
404
|
+
thread_id=str(sync_result["thread_id"]),
|
|
405
|
+
episode_id=str(sync_result["episode_id"]),
|
|
406
|
+
transcript_path=Path(str(sync_result["transcript_path"])),
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
except Exception:
|
|
410
|
+
model_usage_records = ()
|
|
395
411
|
except Exception as exc:
|
|
396
412
|
error_stage = "sync"
|
|
397
413
|
result = _error_response([ErrorDetail(code=ErrorCode.INTERNAL_ERROR, message=str(exc))])
|
|
@@ -436,6 +452,7 @@ def handle_events(
|
|
|
436
452
|
selection_summary=selection_summary,
|
|
437
453
|
sync_run=sync_run,
|
|
438
454
|
sync_tool_types=sync_tool_types,
|
|
455
|
+
model_usage_records=model_usage_records,
|
|
439
456
|
total_latency_ms=int((perf_counter() - started_at) * 1000),
|
|
440
457
|
)
|
|
441
458
|
return result
|
|
@@ -653,6 +670,7 @@ def _persist_operation_telemetry_best_effort(
|
|
|
653
670
|
selection_summary: SessionSelectionSummary | None = None,
|
|
654
671
|
sync_run=None,
|
|
655
672
|
sync_tool_types=(),
|
|
673
|
+
model_usage_records=(),
|
|
656
674
|
total_latency_ms: int | None = None,
|
|
657
675
|
) -> None:
|
|
658
676
|
"""Persist invocation telemetry in a second best-effort transaction."""
|
|
@@ -716,6 +734,11 @@ def _persist_operation_telemetry_best_effort(
|
|
|
716
734
|
run=sync_run,
|
|
717
735
|
tool_types=sync_tool_types,
|
|
718
736
|
)
|
|
737
|
+
if model_usage_records:
|
|
738
|
+
record_model_usage_telemetry(
|
|
739
|
+
uow=telemetry_uow,
|
|
740
|
+
records=tuple(model_usage_records),
|
|
741
|
+
)
|
|
719
742
|
except Exception:
|
|
720
743
|
return
|
|
721
744
|
|
|
@@ -230,6 +230,15 @@ _ANALYTICS_HELP = dedent(
|
|
|
230
230
|
"""
|
|
231
231
|
)
|
|
232
232
|
|
|
233
|
+
_BACKFILL_TOKEN_USAGE_HELP = dedent(
|
|
234
|
+
"""\
|
|
235
|
+
Backfill normalized model-token telemetry from Shellbrain-linked host session files.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
shellbrain admin backfill-token-usage
|
|
239
|
+
"""
|
|
240
|
+
)
|
|
241
|
+
|
|
233
242
|
_METRICS_HELP = dedent(
|
|
234
243
|
"""\
|
|
235
244
|
Generate one lightweight repo-scoped metrics snapshot, write local artifacts, and open a static dashboard.
|
|
@@ -470,6 +479,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
470
479
|
default=2,
|
|
471
480
|
help="Number of trailing days to include in the report. Defaults to 2.",
|
|
472
481
|
)
|
|
482
|
+
admin_subparsers.add_parser(
|
|
483
|
+
"backfill-token-usage",
|
|
484
|
+
help="Backfill normalized token usage from linked host session files.",
|
|
485
|
+
description="Backfill normalized token usage from Shellbrain-linked host session files.",
|
|
486
|
+
epilog=_BACKFILL_TOKEN_USAGE_HELP,
|
|
487
|
+
formatter_class=_HelpFormatter,
|
|
488
|
+
)
|
|
473
489
|
install_hook_parser = admin_subparsers.add_parser(
|
|
474
490
|
"install-claude-hook",
|
|
475
491
|
help="Install the repo-local Claude hook used for trusted caller identity.",
|
|
@@ -771,6 +787,14 @@ def _run_admin_command(args: argparse.Namespace) -> int:
|
|
|
771
787
|
print(json.dumps(report, indent=2, sort_keys=True))
|
|
772
788
|
return 0
|
|
773
789
|
|
|
790
|
+
if args.admin_command == "backfill-token-usage":
|
|
791
|
+
from app.boot.db import get_engine_instance
|
|
792
|
+
from app.periphery.admin.model_usage_backfill import backfill_model_usage
|
|
793
|
+
|
|
794
|
+
summary = backfill_model_usage(engine=get_engine_instance())
|
|
795
|
+
print(json.dumps(summary.to_payload(), indent=2, sort_keys=True))
|
|
796
|
+
return 0
|
|
797
|
+
|
|
774
798
|
repo_root = _resolve_admin_repo_root(getattr(args, "repo_root", None))
|
|
775
799
|
if args.admin_command == "install-claude-hook":
|
|
776
800
|
from app.periphery.identity.claude_hook_install import install_claude_hook
|