superlocalmemory 3.6.23 → 3.7.1
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/CHANGELOG.md +61 -0
- package/README.md +271 -71
- package/bin/slm-npm +43 -89
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +17 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/manifest.json +1 -1
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +7 -7
- package/src/superlocalmemory/cli/commands.py +453 -191
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +32 -23
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +1 -1
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +1 -1
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +130 -31
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +12 -12
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +8 -63
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -1,490 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
-
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
4
|
-
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
5
|
-
|
|
6
|
-
"""dogfood_savings.py — WP-14 metered-pipeline $-savings evidence script.
|
|
7
|
-
|
|
8
|
-
Drives a repeated-prompt workload through the production CacheManager +
|
|
9
|
-
MetricsCollector, reads the MetricsSnapshot delta, and emits a Report
|
|
10
|
-
(JSON ledger + Markdown) with reproducible $ savings.
|
|
11
|
-
|
|
12
|
-
RESEARCH INTEGRITY: Every $ number traces to a real (tokens_saved × price)
|
|
13
|
-
computation from actual cache/compress stats. No hardcoded percentages.
|
|
14
|
-
Prices cite R6 (2026-06-16). OpenAI prices are UNVERIFIED-OFFICIAL.
|
|
15
|
-
|
|
16
|
-
Usage:
|
|
17
|
-
# Sim (no API key, no network, CI-safe):
|
|
18
|
-
python3 scripts/dogfood_savings.py --mode sim --provider anthropic
|
|
19
|
-
|
|
20
|
-
# Live (ONE manual run by Varun, ~$0.05-0.20):
|
|
21
|
-
SLM_DOGFOOD_API_KEY=sk-... python3 scripts/dogfood_savings.py \\
|
|
22
|
-
--mode live --i-will-spend-money --provider anthropic
|
|
23
|
-
|
|
24
|
-
AC6: proxy state is restored in try/finally — including on exception.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
from __future__ import annotations
|
|
28
|
-
|
|
29
|
-
import argparse
|
|
30
|
-
import contextlib
|
|
31
|
-
import json
|
|
32
|
-
import os
|
|
33
|
-
import sys
|
|
34
|
-
import tempfile
|
|
35
|
-
from pathlib import Path
|
|
36
|
-
from typing import Iterator
|
|
37
|
-
|
|
38
|
-
# ---------------------------------------------------------------------------
|
|
39
|
-
# Path setup — allow running as scripts/dogfood_savings.py from repo root
|
|
40
|
-
# ---------------------------------------------------------------------------
|
|
41
|
-
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
42
|
-
_SRC = _REPO_ROOT / "src"
|
|
43
|
-
_SCRIPTS = _REPO_ROOT / "scripts"
|
|
44
|
-
|
|
45
|
-
if str(_SRC) not in sys.path:
|
|
46
|
-
sys.path.insert(0, str(_SRC))
|
|
47
|
-
if str(_SCRIPTS) not in sys.path:
|
|
48
|
-
sys.path.insert(0, str(_SCRIPTS))
|
|
49
|
-
|
|
50
|
-
from _savings_math import ( # noqa: E402 — import after path setup
|
|
51
|
-
MetricsDelta,
|
|
52
|
-
Report,
|
|
53
|
-
compute_savings_usd,
|
|
54
|
-
metrics_delta,
|
|
55
|
-
resolve_price,
|
|
56
|
-
to_inr,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# ---------------------------------------------------------------------------
|
|
60
|
-
# Constants (from optimize_constants.py — read-only)
|
|
61
|
-
# ---------------------------------------------------------------------------
|
|
62
|
-
# We import here to maintain contract-twin parity with optimize_cmd.py:145-155.
|
|
63
|
-
from superlocalmemory.cli.optimize_constants import ( # noqa: E402
|
|
64
|
-
DEFAULT_COST_PER_MILLION_INPUT_TOKENS,
|
|
65
|
-
_PRICING_DATE,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
_DEFAULT_PROMPTS = 20
|
|
69
|
-
_DEFAULT_REPEAT = 5
|
|
70
|
-
_DEFAULT_PROVIDER = "anthropic"
|
|
71
|
-
_DEFAULT_INR_RATE = 83.5
|
|
72
|
-
|
|
73
|
-
# Openai prices are from R6 but UNVERIFIED-OFFICIAL.
|
|
74
|
-
_OPENAI_UNVERIFIED_NOTE = "UNVERIFIED-OFFICIAL"
|
|
75
|
-
|
|
76
|
-
# Output paths per LLD §2 / D-B LOCKED
|
|
77
|
-
_EVIDENCE_DIR = _REPO_ROOT / ".backup" / "v3.6.14" / "evidence" / "wp14"
|
|
78
|
-
_DOCS_EVIDENCE_MD = _REPO_ROOT / "docs" / "evidence" / "optimize-savings.md"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# ---------------------------------------------------------------------------
|
|
82
|
-
# Sim workload
|
|
83
|
-
# ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
def _build_sim_prompt(index: int) -> str:
|
|
86
|
-
"""Build a char-exact prompt so chars//4 = integer tokens exactly.
|
|
87
|
-
|
|
88
|
-
Pattern: 40 chars (10 tokens) per prompt, varied by index.
|
|
89
|
-
This ensures M-01 (chars//4) produces predictable, non-zero values.
|
|
90
|
-
"""
|
|
91
|
-
# 40 chars = 10 tokens exactly via chars // 4 = 40 // 4 = 10
|
|
92
|
-
base = f"Prompt{index:04d}: What is the capital of France? "
|
|
93
|
-
# Pad/truncate to exactly 40 chars
|
|
94
|
-
padded = base[:40].ljust(40)
|
|
95
|
-
return padded
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _build_sim_response(output_tokens: int = 8) -> dict:
|
|
99
|
-
"""Build a synthetic provider response with a usage block.
|
|
100
|
-
|
|
101
|
-
The usage block is required for M-02 (output token recovery) in on_hit.
|
|
102
|
-
"""
|
|
103
|
-
return {
|
|
104
|
-
"id": "sim-resp-001",
|
|
105
|
-
"type": "message",
|
|
106
|
-
"role": "assistant",
|
|
107
|
-
"content": [{"type": "text", "text": "Paris is the capital of France."}],
|
|
108
|
-
"stop_reason": "end_turn",
|
|
109
|
-
"usage": {
|
|
110
|
-
"input_tokens": 10,
|
|
111
|
-
"output_tokens": output_tokens,
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def run_workload_sim(
|
|
117
|
-
n_prompts: int,
|
|
118
|
-
n_repeat: int,
|
|
119
|
-
tmp_db_path: Path,
|
|
120
|
-
) -> None:
|
|
121
|
-
"""Drive N prompts × (1 miss + R hits) through production CacheManager.
|
|
122
|
-
|
|
123
|
-
Uses a temp CacheDB — never touches user's llmcache.db (AC8 / LLD §8).
|
|
124
|
-
Synthetic responses include a `usage` block so M-02 recovers output tokens.
|
|
125
|
-
"""
|
|
126
|
-
import superlocalmemory.optimize.storage.db as _db_mod
|
|
127
|
-
from superlocalmemory.optimize.cache.manager import CacheManager
|
|
128
|
-
from superlocalmemory.optimize.metrics.counters import MetricsCollector
|
|
129
|
-
from superlocalmemory.optimize.proxy.lifecycle import ProxyRequest
|
|
130
|
-
from superlocalmemory.optimize.storage.db import CacheDB
|
|
131
|
-
|
|
132
|
-
# Isolate AES key to tmp dir (mirrors tests/optimize/conftest.py pattern)
|
|
133
|
-
original_key_file = getattr(_db_mod, "_KEY_FILE", None)
|
|
134
|
-
_db_mod._KEY_FILE = tmp_db_path.parent / "opt-key.bin"
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
cache_db = CacheDB(tmp_db_path)
|
|
138
|
-
cm = CacheManager(cache_db)
|
|
139
|
-
# Reset MetricsCollector singleton for isolation
|
|
140
|
-
MetricsCollector._instance = None
|
|
141
|
-
collector = MetricsCollector.get_instance()
|
|
142
|
-
|
|
143
|
-
for i in range(n_prompts):
|
|
144
|
-
prompt_text = _build_sim_prompt(i)
|
|
145
|
-
sim_response = _build_sim_response()
|
|
146
|
-
resp_bytes = json.dumps(sim_response).encode()
|
|
147
|
-
|
|
148
|
-
# Build a ProxyRequest (immutable)
|
|
149
|
-
req = ProxyRequest(
|
|
150
|
-
provider="anthropic",
|
|
151
|
-
method="POST",
|
|
152
|
-
path="/v1/messages",
|
|
153
|
-
headers={}, # redacted per CWE-532
|
|
154
|
-
body={
|
|
155
|
-
"model": "claude-sonnet-4-6",
|
|
156
|
-
"messages": [{"role": "user", "content": prompt_text}],
|
|
157
|
-
"max_tokens": 50,
|
|
158
|
-
},
|
|
159
|
-
body_bytes=b"",
|
|
160
|
-
request_id=f"sim-{i:04d}",
|
|
161
|
-
stream=False,
|
|
162
|
-
has_tools=False,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
# First call: miss (store response)
|
|
166
|
-
hit = cm.check(req)
|
|
167
|
-
if hit is None or not hit.hit:
|
|
168
|
-
from superlocalmemory.optimize.proxy.lifecycle import ProviderResponse
|
|
169
|
-
prov_resp = ProviderResponse(
|
|
170
|
-
modified=False,
|
|
171
|
-
body=sim_response,
|
|
172
|
-
body_bytes=resp_bytes,
|
|
173
|
-
tokens_before=0,
|
|
174
|
-
tokens_after=0,
|
|
175
|
-
strategy="none",
|
|
176
|
-
)
|
|
177
|
-
cm.store(req, prov_resp)
|
|
178
|
-
|
|
179
|
-
# Repeat calls: hits (tokens recovered via on_hit M-01/M-02)
|
|
180
|
-
for _ in range(n_repeat):
|
|
181
|
-
hit = cm.check(req)
|
|
182
|
-
if hit is not None and hit.hit:
|
|
183
|
-
cm.on_hit(req, hit.data or resp_bytes, tokens_saved=0)
|
|
184
|
-
else:
|
|
185
|
-
# Should not happen in sim; record miss for safety
|
|
186
|
-
collector.on_miss()
|
|
187
|
-
|
|
188
|
-
finally:
|
|
189
|
-
if original_key_file is not None:
|
|
190
|
-
_db_mod._KEY_FILE = original_key_file
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# ---------------------------------------------------------------------------
|
|
194
|
-
# Proxy context manager (for live mode, AC6)
|
|
195
|
-
# ---------------------------------------------------------------------------
|
|
196
|
-
|
|
197
|
-
@contextlib.contextmanager
|
|
198
|
-
def with_proxy_enabled() -> Iterator[None]:
|
|
199
|
-
"""Enable proxy for the duration of the block; restore in finally.
|
|
200
|
-
|
|
201
|
-
Reads optimize.json, sets enable_cache=True, restores on exit (AC6).
|
|
202
|
-
Only safe for ONE concurrent user — live docs "no other proxy traffic".
|
|
203
|
-
"""
|
|
204
|
-
from superlocalmemory.optimize.config.store import ConfigStore
|
|
205
|
-
|
|
206
|
-
store = ConfigStore()
|
|
207
|
-
cfg = store.get()
|
|
208
|
-
original_enable_cache = cfg.enable_cache
|
|
209
|
-
original_enable_compress = cfg.enable_compress
|
|
210
|
-
|
|
211
|
-
try:
|
|
212
|
-
# Enable cache for the live run
|
|
213
|
-
new_cfg = store.get()
|
|
214
|
-
new_cfg.enable_cache = True
|
|
215
|
-
store.save(new_cfg)
|
|
216
|
-
yield
|
|
217
|
-
finally:
|
|
218
|
-
# Restore exactly (AC6 — even on exception)
|
|
219
|
-
restore_cfg = store.get()
|
|
220
|
-
restore_cfg.enable_cache = original_enable_cache
|
|
221
|
-
restore_cfg.enable_compress = original_enable_compress
|
|
222
|
-
store.save(restore_cfg)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# ---------------------------------------------------------------------------
|
|
226
|
-
# Live workload (AC4 gated)
|
|
227
|
-
# ---------------------------------------------------------------------------
|
|
228
|
-
|
|
229
|
-
def run_workload_live(
|
|
230
|
-
n_prompts: int,
|
|
231
|
-
n_repeat: int,
|
|
232
|
-
api_key: str,
|
|
233
|
-
provider: str,
|
|
234
|
-
) -> None:
|
|
235
|
-
"""Route real HTTP through proxy at 127.0.0.1:8765.
|
|
236
|
-
|
|
237
|
-
SECURITY: api_key comes from env only, NEVER logged or printed.
|
|
238
|
-
This function is excluded from CI coverage (live path).
|
|
239
|
-
"""
|
|
240
|
-
import urllib.request # stdlib only — no new deps
|
|
241
|
-
|
|
242
|
-
port = 8765
|
|
243
|
-
base_url = f"http://127.0.0.1:{port}"
|
|
244
|
-
|
|
245
|
-
headers = {
|
|
246
|
-
"Content-Type": "application/json",
|
|
247
|
-
"x-api-key": api_key, # never logged
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
prompt = "What is 2 + 2? Answer in one word."
|
|
251
|
-
|
|
252
|
-
for i in range(n_prompts):
|
|
253
|
-
body = json.dumps({
|
|
254
|
-
"model": "claude-haiku-4-5", # cheapest Anthropic model
|
|
255
|
-
"messages": [{"role": "user", "content": f"[{i}] {prompt}"}],
|
|
256
|
-
"max_tokens": 16,
|
|
257
|
-
}).encode()
|
|
258
|
-
|
|
259
|
-
# First call: miss
|
|
260
|
-
req = urllib.request.Request(
|
|
261
|
-
f"{base_url}/anthropic/v1/messages",
|
|
262
|
-
data=body,
|
|
263
|
-
headers=headers,
|
|
264
|
-
method="POST",
|
|
265
|
-
)
|
|
266
|
-
with urllib.request.urlopen(req, timeout=30) as resp: # noqa: S310
|
|
267
|
-
resp.read()
|
|
268
|
-
|
|
269
|
-
# Repeat calls: hits
|
|
270
|
-
for _ in range(n_repeat):
|
|
271
|
-
req2 = urllib.request.Request(
|
|
272
|
-
f"{base_url}/anthropic/v1/messages",
|
|
273
|
-
data=body,
|
|
274
|
-
headers=headers,
|
|
275
|
-
method="POST",
|
|
276
|
-
)
|
|
277
|
-
with urllib.request.urlopen(req2, timeout=30) as resp2: # noqa: S310
|
|
278
|
-
resp2.read()
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
# ---------------------------------------------------------------------------
|
|
282
|
-
# Snapshot helpers
|
|
283
|
-
# ---------------------------------------------------------------------------
|
|
284
|
-
|
|
285
|
-
def _snapshot_from_collector() -> object:
|
|
286
|
-
"""Read current MetricsCollector state as a MetricsSnapshot-compatible object."""
|
|
287
|
-
from superlocalmemory.optimize.metrics.counters import MetricsCollector
|
|
288
|
-
from superlocalmemory.optimize.storage.db import MetricsSnapshot
|
|
289
|
-
|
|
290
|
-
c = MetricsCollector.get_instance()
|
|
291
|
-
snap = c.snapshot() if hasattr(c, "snapshot") else None
|
|
292
|
-
if snap is not None:
|
|
293
|
-
return snap
|
|
294
|
-
# Fallback: read fields directly
|
|
295
|
-
return MetricsSnapshot(
|
|
296
|
-
hits=c._hits,
|
|
297
|
-
misses=c._misses,
|
|
298
|
-
tokens_saved_input=c._tokens_saved_input,
|
|
299
|
-
tokens_saved_output=c._tokens_saved_output,
|
|
300
|
-
tokens_saved_compress=c._tokens_saved_compress,
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def _zero_snapshot() -> object:
|
|
305
|
-
"""Return a zeroed MetricsSnapshot."""
|
|
306
|
-
from superlocalmemory.optimize.storage.db import MetricsSnapshot
|
|
307
|
-
return MetricsSnapshot()
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
# ---------------------------------------------------------------------------
|
|
311
|
-
# Main
|
|
312
|
-
# ---------------------------------------------------------------------------
|
|
313
|
-
|
|
314
|
-
def _build_price_source(provider: str, price: float, source_key: str) -> str:
|
|
315
|
-
"""Build a human-readable price source string for the report."""
|
|
316
|
-
date = _PRICING_DATE
|
|
317
|
-
r6 = "R6 (2026-06-16)"
|
|
318
|
-
if provider.lower() == "openai":
|
|
319
|
-
return (
|
|
320
|
-
f"DEFAULT_COST_PER_MILLION_INPUT_TOKENS[{provider}]={price}, "
|
|
321
|
-
f"{date}, {r6}, {_OPENAI_UNVERIFIED_NOTE}"
|
|
322
|
-
)
|
|
323
|
-
return f"{source_key}={price}, {date}, {r6}, VERIFIED (Anthropic official)"
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
def main() -> int:
|
|
327
|
-
parser = argparse.ArgumentParser(
|
|
328
|
-
description="WP-14: metered-pipeline $-savings dogfood evidence script.",
|
|
329
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
330
|
-
epilog=(
|
|
331
|
-
"RESEARCH INTEGRITY: Every $ number traces to tokens_saved × price.\n"
|
|
332
|
-
"Sim mode is CI-safe (no key, no network). Live mode costs real money.\n"
|
|
333
|
-
"OpenAI prices: UNVERIFIED-OFFICIAL (see R6 2026-06-16).\n"
|
|
334
|
-
),
|
|
335
|
-
)
|
|
336
|
-
parser.add_argument(
|
|
337
|
-
"--mode", choices=["sim", "live"], default="sim",
|
|
338
|
-
help="sim = offline/CI-safe; live = real HTTP via proxy (costs money)",
|
|
339
|
-
)
|
|
340
|
-
parser.add_argument(
|
|
341
|
-
"--provider", default=_DEFAULT_PROVIDER,
|
|
342
|
-
choices=["anthropic", "openai", "gemini"],
|
|
343
|
-
help="Provider key for pricing lookup (default: anthropic)",
|
|
344
|
-
)
|
|
345
|
-
parser.add_argument(
|
|
346
|
-
"--prompts", type=int, default=_DEFAULT_PROMPTS,
|
|
347
|
-
help="Number of distinct prompts (N). Default: 20",
|
|
348
|
-
)
|
|
349
|
-
parser.add_argument(
|
|
350
|
-
"--repeat", type=int, default=_DEFAULT_REPEAT,
|
|
351
|
-
help="Number of cache-hit repeats per prompt (R). Default: 5",
|
|
352
|
-
)
|
|
353
|
-
parser.add_argument(
|
|
354
|
-
"--i-will-spend-money", action="store_true",
|
|
355
|
-
help="Required for --mode live. Confirms you accept real API spend.",
|
|
356
|
-
)
|
|
357
|
-
parser.add_argument(
|
|
358
|
-
"--out-md", type=Path, default=None,
|
|
359
|
-
help="Output path for Markdown report (default: docs/evidence/optimize-savings.md)",
|
|
360
|
-
)
|
|
361
|
-
parser.add_argument(
|
|
362
|
-
"--out-json", type=Path, default=None,
|
|
363
|
-
help="Output path for JSON ledger (default: .backup/v3.6.14/evidence/wp14/raw-metrics-<mode>-<utc>.json)",
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
args = parser.parse_args()
|
|
367
|
-
|
|
368
|
-
# AC4: live mode guards
|
|
369
|
-
if args.mode == "live":
|
|
370
|
-
if not getattr(args, "i_will_spend_money", False):
|
|
371
|
-
print(
|
|
372
|
-
"ERROR: --mode live requires --i-will-spend-money flag.\n"
|
|
373
|
-
"This mode routes real requests through the proxy and costs real money.",
|
|
374
|
-
file=sys.stderr,
|
|
375
|
-
)
|
|
376
|
-
return 2
|
|
377
|
-
|
|
378
|
-
api_key = os.environ.get("SLM_DOGFOOD_API_KEY", "")
|
|
379
|
-
if not api_key:
|
|
380
|
-
print(
|
|
381
|
-
"ERROR: SLM_DOGFOOD_API_KEY environment variable not set.\n"
|
|
382
|
-
"Set it to your Anthropic API key before running --mode live.",
|
|
383
|
-
file=sys.stderr,
|
|
384
|
-
)
|
|
385
|
-
return 2
|
|
386
|
-
|
|
387
|
-
# Resolve price (parity with optimize_cmd.py:146-153)
|
|
388
|
-
price, price_source_key = resolve_price(
|
|
389
|
-
args.provider,
|
|
390
|
-
DEFAULT_COST_PER_MILLION_INPUT_TOKENS,
|
|
391
|
-
{}, # no config overrides in dogfood — uses defaults for reproducibility
|
|
392
|
-
)
|
|
393
|
-
price_source = _build_price_source(args.provider, price, price_source_key)
|
|
394
|
-
|
|
395
|
-
# ---------------------------------------------------------------------------
|
|
396
|
-
# Run workload and collect delta
|
|
397
|
-
# ---------------------------------------------------------------------------
|
|
398
|
-
|
|
399
|
-
if args.mode == "sim":
|
|
400
|
-
with tempfile.TemporaryDirectory() as _tmp:
|
|
401
|
-
tmp_dir = Path(_tmp)
|
|
402
|
-
tmp_db = tmp_dir / "llmcache.db"
|
|
403
|
-
|
|
404
|
-
# Capture before (zeroed — fresh temp DB)
|
|
405
|
-
before = _zero_snapshot()
|
|
406
|
-
|
|
407
|
-
run_workload_sim(args.prompts, args.repeat, tmp_db)
|
|
408
|
-
|
|
409
|
-
# Read after from the MetricsCollector (which on_hit updated)
|
|
410
|
-
after = _snapshot_from_collector()
|
|
411
|
-
|
|
412
|
-
else:
|
|
413
|
-
# Live mode: use real CacheDB via proxy
|
|
414
|
-
before_db = _read_live_snapshot()
|
|
415
|
-
with with_proxy_enabled():
|
|
416
|
-
api_key = os.environ["SLM_DOGFOOD_API_KEY"] # already validated above
|
|
417
|
-
run_workload_live(args.prompts, args.repeat, api_key, args.provider)
|
|
418
|
-
after_db = _read_live_snapshot()
|
|
419
|
-
before = before_db
|
|
420
|
-
after = after_db
|
|
421
|
-
|
|
422
|
-
# ---------------------------------------------------------------------------
|
|
423
|
-
# Compute savings
|
|
424
|
-
# ---------------------------------------------------------------------------
|
|
425
|
-
|
|
426
|
-
delta = metrics_delta(before, after)
|
|
427
|
-
savings_usd = compute_savings_usd(
|
|
428
|
-
delta.tokens_saved_input,
|
|
429
|
-
delta.tokens_saved_output,
|
|
430
|
-
delta.tokens_saved_compress,
|
|
431
|
-
price_per_1m=price,
|
|
432
|
-
)
|
|
433
|
-
savings_inr = to_inr(savings_usd, _DEFAULT_INR_RATE)
|
|
434
|
-
|
|
435
|
-
report = Report(
|
|
436
|
-
mode=args.mode,
|
|
437
|
-
provider=args.provider,
|
|
438
|
-
prompts=args.prompts,
|
|
439
|
-
repeat=args.repeat,
|
|
440
|
-
delta=delta,
|
|
441
|
-
price_per_1m=price,
|
|
442
|
-
price_source=price_source,
|
|
443
|
-
savings_usd=savings_usd,
|
|
444
|
-
savings_inr=savings_inr,
|
|
445
|
-
inr_rate=_DEFAULT_INR_RATE,
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
# ---------------------------------------------------------------------------
|
|
449
|
-
# Write artifacts
|
|
450
|
-
# ---------------------------------------------------------------------------
|
|
451
|
-
|
|
452
|
-
import datetime as _dt
|
|
453
|
-
|
|
454
|
-
utc_tag = _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
455
|
-
|
|
456
|
-
# JSON ledger path
|
|
457
|
-
if args.out_json is not None:
|
|
458
|
-
json_path = Path(args.out_json)
|
|
459
|
-
else:
|
|
460
|
-
_EVIDENCE_DIR.mkdir(parents=True, exist_ok=True)
|
|
461
|
-
json_path = _EVIDENCE_DIR / f"raw-metrics-{args.mode}-{utc_tag}.json"
|
|
462
|
-
|
|
463
|
-
# MD report path
|
|
464
|
-
if args.out_md is not None:
|
|
465
|
-
md_path = Path(args.out_md)
|
|
466
|
-
else:
|
|
467
|
-
md_path = _DOCS_EVIDENCE_MD
|
|
468
|
-
|
|
469
|
-
json_path.parent.mkdir(parents=True, exist_ok=True)
|
|
470
|
-
md_path.parent.mkdir(parents=True, exist_ok=True)
|
|
471
|
-
|
|
472
|
-
json_path.write_text(report.to_json(), encoding="utf-8")
|
|
473
|
-
md_path.write_text(report.to_markdown(), encoding="utf-8")
|
|
474
|
-
|
|
475
|
-
print(report.to_markdown())
|
|
476
|
-
print(f"\nJSON ledger: {json_path}")
|
|
477
|
-
print(f"MD report: {md_path}")
|
|
478
|
-
|
|
479
|
-
return 0
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
def _read_live_snapshot() -> object:
|
|
483
|
-
"""Read snapshot from the live CacheDB (for --mode live)."""
|
|
484
|
-
from superlocalmemory.optimize.storage.db import CacheDB
|
|
485
|
-
db = CacheDB.get_default()
|
|
486
|
-
return db.metrics_load()
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if __name__ == "__main__":
|
|
490
|
-
raise SystemExit(main())
|