Commit graph

1553 commits

Author SHA1 Message Date
chopratejas
e2aac4863a Add wrap commands for Codex/Cursor/Aider with rtk instructions, fix savings metrics
- headroom wrap codex: injects rtk instructions into AGENTS.md
- headroom wrap cursor: injects into .cursorrules, prints config steps
- headroom wrap aider: injects into CONVENTIONS.md, sets both env vars
- All with --no-rtk flag to skip rtk setup

Unified savings metrics across three layers:
- cli_filtering: tokens avoided by rtk before reaching context
- compression: tokens removed by proxy (SmartCrusher, etc.)
- prefix_cache: provider cache discount with honest attribution

Fix flaky test_process_stats_collected when psutil not installed.
2026-03-13 22:02:47 -07:00
chopratejas
93d41b66b1 Commit message:
Fix OpenAI streaming with backends and /v1 double-path bug

Add stream_openai_message() to LiteLLM and any-llm backends so
/v1/chat/completions with stream:true returns SSE events instead
of a JSON blob. Clients (Kilo Code, Cursor, etc.) were hanging
because the proxy ignored the stream flag when routing through
a backend.

Also strip trailing /v1 from OPENAI_TARGET_API_URL to prevent
double-path URLs like /v1/v1/models.
2026-03-13 16:49:18 -07:00
chopratejas
18118af5ce Slim core dependencies: 2.5GB → 195MB install size
Move heavy deps to optional extras: sentence-transformers, torch,
numpy, pillow, datasets, accelerate out of core. Remove unused deps
entirely (semantic-router, protobuf, sentencepiece).

New extras: [ml] for Kompress, [image] for image compression,
[langchain] for LangChain integration. Guard memory/image imports
so core install works without numpy/torch.

Core (tiktoken, pydantic, litellm, click, rich) gives full
compression: SmartCrusher, ContentRouter, CCR, TOIN, CLI.
2026-03-13 15:35:49 -07:00
chopratejas
d2e88d362a Fix pricing lookup for retired Claude models
LiteLLM removed claude-3-5-sonnet-20241022 from its cost database.
Add alias fallback map so retired model names resolve to current
equivalents for pricing lookups.
2026-03-12 21:11:18 -07:00
chopratejas
4bea17ba8a Fix proxy backend bugs: env vars, tool forwarding, and provider support
- CLI now reads OPENAI_TARGET_API_URL, GEMINI_TARGET_API_URL, and
HEADROOM_ANYLLM_PROVIDER environment variables
- Add --openai-api-url and --gemini-api-url CLI flags
- Remove --backend choices restriction so litellm-* backends work
- Forward tools/tool_choice through LiteLLM and any-llm backends
- Parse tool call arguments from JSON string to dict (Anthropic format)
- Forward top_p, stop_sequences, tools in streaming paths
- Update Vertex AI model map with Claude 3 through 4.6 (from official docs)
- Bump version to 0.4.1
2026-03-12 20:55:26 -07:00
chopratejas
8f438b0674 Introducing headroom wrap
- headroom wrap claude is the simplest way to start up claude
- It will also install rtk-ai locally
- rtk-ai is a cli wrapper that can save ~90% tokens for CLI calls made by Claude Code
2026-03-11 23:01:42 -07:00
chopratejas
df1705549f Add Kompress: ModernBERT token compressor replacing LLMLingua-2
Adds kompress_compressor.py — a self-contained ModernBERT-based token
compressor that auto-downloads from chopratejas/kompress-base on HuggingFace.
Trained on 330K structured tool outputs (JSON, diffs, logs, code, SQL,
agentic traces), achieving 82% entity preservation vs LLMLingua-2's 36%.

Changes:
- New: kompress_compressor.py — dual-head ModernBERT (token + span CNN)
with HuggingFace auto-download, no extra pip install needed
- ContentRouter: Kompress is primary ML compressor, LLMLingua-2 is fallback
- fallback_strategy changed from PASSTHROUGH to KOMPRESS — unknown/mixed
content now gets compressed instead of ignored
- No hardcoded compression ratios — model decides per-token importance,
optional target_ratio only when user explicitly sets it via API
- Version bump: 0.3.8 → 0.4.0
2026-03-10 18:45:02 -07:00
Tejas Chopra
4a655fbc6e feat: provider-aware prefix cache tracking and combined savings dashboard
Add per-provider prefix cache metrics (Anthropic/OpenAI/Google/Bedrock)
with correct economics (read discounts, write premiums, bust detection).
Model-aware bust detection excludes cold starts when switching models.
Dashboard hero metric shows combined savings (compression + cache) with
per-provider breakdown table, efficiency bar, and hit rate tracking.

- Add _CACHE_ECONOMICS dict and _build_prefix_cache_stats() helper
- Track cache_by_provider with per-model cold start awareness
- Add _merge_cost_stats() to combine compression + cache savings
- Dashboard: "Prefix Cache Impact" section with provider breakdown
- Dashboard: hero "Total Savings" shows compress + cache breakdown
- Fix ruff (unused var, quoted annotations) and mypy type errors
- Refactor code_compressor to data-driven language config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:14:15 -07:00
Tejas Chopra
08d81f2e2c fix: dashboard metrics, TTFB tracking, eager LLMLingua loading, and multi-provider consistency
Dashboard was showing wildly incorrect metrics (99.5% savings, 3ms overhead)
due to using Anthropic API's non-cached input_tokens instead of optimized_tokens,
and dividing overhead by total request count instead of optimized-only count.

Key fixes:
- Use optimized_tokens (what we sent) for dashboard aggregation, not API's
  input_tokens which excludes cached portion
- Track overhead_count separately from latency_count for correct averages
- Add TTFB (time to first byte) measurement, replace full stream latency in UI
- Eager-load LLMLingua model at proxy startup (eliminates 5.9s first-request delay)
- Simplify CostTracker to token-based accounting with counterfactual cost display
- Add two-tier compression cache to ContentRouter (skip set + result cache)
- Fix compression pinning to detect both CCR and ReadLifecycle markers
- Clamp tokens_saved to max(0, ...) across all provider paths
- Add per-transform timing instrumentation to pipeline
- Guard against over-aggressive code compression (<5% ratio)
- Fix ReadLifecycle partial read supersede logic (_read_covers range check)
- Disable CacheAligner and compress_superseded by default
- Fix all pre-existing mypy errors (CompressionCache return types)
- Fix test mocks to accept **kwargs for cache token parameters
2026-03-07 23:33:45 -08:00
Tejas Chopra
da481a359b fix(learn): pass explicit model in tests to avoid API key requirement
SessionAnalyzer() without a model calls _detect_default_model() which
raises when no API keys are set (e.g., in CI). Pass model="test-model"
in the three tests that mock _call_llm.
2026-03-07 15:10:25 -08:00
Tejas Chopra
4d14012c2f feat: add headroom perf CLI and rewrite headroom learn to use LLM analysis
Proxy performance logging (`headroom perf`):
- Add always-on RotatingFileHandler to ~/.headroom/logs/proxy.log (10MB x 5 backups)
- Replace scattered log lines with structured PERF lines containing model, msgs,
  tok_before/after/saved, cache_read/write/hit_pct, opt_ms, and transforms
- Emit PERF lines from all three response paths (streaming Anthropic, non-streaming
  Anthropic, Bedrock streaming)
- Add `headroom perf` CLI that parses proxy logs and reports token savings, cache
  hit rates, prefix stability, transform effectiveness, routing breakdown, TOIN
  status, and actionable recommendations
- Support --hours and --raw flags for time filtering and raw record output

Learn module rewrite (LLM-based analysis):
- Replace all regex/heuristic analyzers with a single LLM call via LiteLLM
- New SessionAnalyzer builds compact digests and sends to any of 100+ models
- Auto-detect best model from API keys (Anthropic → OpenAI → Gemini)
- Add --model flag for explicit model selection
- Enrich scanner with SessionEvent (user messages, interruptions, subagent summaries),
  token usage tracking, and timestamps
- Simplify models: remove EnvironmentFact, StructureNote, Correction, CommandPattern,
  RetryPattern, AnalysisReport; add SessionEvent, AnalysisResult
- Simplify writer: remove Recommender class (LLM now produces recommendations directly)
- Update tests for new analyzer and models
2026-03-07 14:15:49 -08:00
Claude Code Bot
fcafa373e7 fix(mcp): fix uninstall symmetry, move subprocess import, add cli-path tests
Address code review feedback on the initial MCP fix:

- mcp_uninstall: now also calls `claude mcp remove -s user` when the claude
  CLI is available, mirroring mcp_install. Removes from mcp.json fallback
  config as well if present. This fixes the broken uninstall->reinstall
  roundtrip on machines using Claude Code CLI >=2.x.

- cli/mcp.py: move `import subprocess` to module level (was deferred inside
  mcp_install function body, inconsistent with other stdlib imports and
  harder to mock).

- tests: add TestMCPInstallWithClaudeCLI and TestMCPUninstallWithClaudeCLI
  covering the previously-untested `claude mcp add` code path, including
  force-overwrite ordering, -e flag for env vars, and fallback on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:02:35 -08:00
Claude Code Bot
c078faa353 fix(mcp): use claude mcp add for install and fix startup import
Two fixes to get the headroom MCP server working with Claude Code CLI:

1. mcp_server.py: define CCR_TOOL_NAME inline instead of importing from
   tool_injection, which triggered headroom/__init__.py -> LiteLLM -> HTTP
   requests to GitHub, adding 4-5 seconds to startup time.

2. cli/mcp.py: prefer `claude mcp add -s user` when the claude CLI is
   available (Claude Code CLI ≥2.x stores servers in ~/.claude/.claude.json,
   not ~/.claude/mcp.json). Falls back to writing mcp.json for older versions
   and the claude.ai desktop app.

3. tests/test_cli/test_mcp.py: update mock_claude_config_path fixture to
   also stub out the claude CLI so install tests exercise the mcp.json
   fallback path, matching the fixture's intent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:46:36 -08:00
Tejas Chopra
655df095fd feat(router): adaptive compression with Read lifecycle and context-pressure scaling
Enable ReadLifecycle by default so stale/superseded Read outputs are
automatically replaced with compact CCR markers — these are provably safe
to compress (file was edited or re-read).

Replace static compression thresholds with adaptive parameters that scale
with conversation length and context pressure:

- protect_recent_reads_fraction: protects the most-recent 50% of messages
  from Read exclusion. Old Reads beyond this window become compressible,
  preventing the "28 excluded Read/Glob, 0 tokens saved" problem.

- min_ratio_relaxed / min_ratio_aggressive: compression acceptance
  threshold interpolates linearly with context pressure (tokens / model
  limit). Low pressure → 0.85 (picky), high pressure → 0.65 (accept
  anything helpful). Eliminates the fixed 0.9 gate that was rejecting
  20+ messages per request.

Also adds --no-read-lifecycle CLI flag, and fixes a missing
pytest.importorskip guard for sentence-transformers in memory tests.
2026-03-06 00:29:53 -08:00
chopratejas
9c31b22bff feat(code): add semantic symbol importance to CodeAwareCompressor
CodeAwareCompressor now analyzes intra-file symbol relationships before
compression, using tree-sitter AST walks to count references, map call
graphs, and detect public/private visibility. This replaces uniform
"keep first N body lines" compression with budget-based allocation driven
by the existing target_compression_rate config.

Key design decisions:
- Distribution-based scoring (min-max normalized within each file) so it
  adapts to any file structure: utility libs, test files, orchestrators
- Budget allocation: target_compression_rate determines total body line
  budget, distributed proportionally to importance × body size
- max_body_lines respected as a hard cap over budget allocation
- Context-aware: the existing `context` parameter now boosts symbols
  matching the user's task (word-boundary matching, not substring)
- Qualified names (ClassName.method) internally to avoid collisions
  between identically-named methods in different classes
- Omitted comments include call graph info from AST analysis
- Zero new dependencies — uses tree-sitter already in headroom[code]
- semantic_analysis=True by default, fully backward-compatible when False
2026-03-03 14:18:41 -08:00
chopratejas
7cf086c2e8 Add multi-agent support, quality gates, and integration tests for headroom learn
- Codex adapter: CodexScanner reads ~/.codex/sessions/*.json, CodexWriter
  writes to AGENTS.md + instructions.md. Tested on 328 real sessions.
- Gemini writer: GeminiWriter writes to GEMINI.md (scanner deferred,
  sessions stored in protobuf).
- CLI --agent flag: auto-detect available agents or specify claude/codex/gemini.
- Quality gates: min_evidence, min_confidence, min_total_evidence thresholds
  prevent weak signals from writing noise to project files.
- Integration tests against real Claude Code and Codex session data on disk.
  Tests skip gracefully if data directories don't exist.
- Bash path extraction for Codex (reads files via sed/cat, not Read tool).
- Idempotency, false positive filtering, and skip-write-on-empty tests.
2026-02-28 23:29:02 -08:00
chopratejas
17442c2dcc Add headroom learn: offline failure learning for coding agents
Analyzes past conversation history to find tool call failure patterns,
correlates each failure with what eventually succeeded, and writes
specific project-level learnings to CLAUDE.md and MEMORY.md.

Key design:
- Success correlation: extracts the diff between failed and successful
  inputs as the learning (not generic advice)
- Generic architecture: tool-agnostic ToolCall model with pluggable
  Scanner/Writer adapters (Claude Code first, extensible to Cursor/Codex)
- 5 analyzers: Environment, Structure, Commands, Retries, Cross-Session
- Dry-run by default, --apply to write, --all for all projects

Also fixes mypy errors in litellm_callback, asgi, langchain chat_model,
and anthropic provider (AsyncClient typing, ToolCall arg-type, int cast).
2026-02-27 21:19:03 -08:00
chopratejas
876949e638 Fix LangChain tool_call argument handling for varied message formats
LangChain provides tool_call args in different shapes (dict args, str
arguments, nested function.arguments) depending on the source. Add
_tool_call_args_to_json() helper to normalize all formats to JSON strings.
Use .get() instead of [] to handle missing keys gracefully.
2026-02-27 20:09:18 -08:00
chopratejas
bc2d4bd6b6 Add Read Lifecycle: event-driven stale/superseded Read detection
Detects Read tool outputs that became stale (file was later edited) or
superseded (file was later re-Read) and replaces them with compact markers
+ CCR hashes. Fresh Reads are never touched.

Adds ReadLifecycleConfig to config.py and integrates ReadLifecycleManager
as a pre-processing pass in ContentRouter. Opt-in via config flag to
preserve backward-compatible behavior.
2026-02-27 20:09:09 -08:00
chopratejas
df1f53efe7 Add Memory Bridge: bidirectional sync between markdown files and Headroom memory
Foundational models (Claude Code, ChatGPT) store memory in flat .md files while
Headroom uses a semantic vector store. This bridge connects the two worlds —
importing .md files into Headroom for semantic search, exporting Headroom memories
back to .md, and keeping them in sync with hash-based change detection.
2026-02-23 22:45:38 -08:00
chopratejas
a2b2ec5463 Add OSS evaluation suite, universal JSON crush, latency benchmarks
Evaluation Suite:
- Tiered eval framework (Tier 1 ~$3/15min, Tier 2/3 for extended coverage)
- 16 benchmarks across 3 tiers: GSM8K, TruthfulQA, MMLU, ARC, HumanEval,
  SQuAD v2, BFCL, Tool Outputs, CCR needle retention, HotpotQA, and more
- Before/After runner with full proxy support (compression + CCR retrieval)
- LLM-as-judge for ground-truth comparison (BFCL function calling)
- Zero-cost compression-only runner (CCR needle retention, info retention)
- Cost tracker with per-model pricing and budget enforcement
- Report card generator (Markdown, JSON, HTML)
- Suite CLI: python -m headroom.evals suite --tier 1
- Fix BFCL dataset loader for current HuggingFace schema
- CI workflow: PR smoke test + weekly full Tier 1

Results: SQuAD 97%, BFCL 97%, Tool Outputs 100%, CCR 100%

SmartCrusher:
- Universal JSON crush for heterogeneous arrays
- Fix mypy redefinition warning in _crush_string_array

Other:
- Latency benchmark suite with docs
- Known limitations doc
- Prompt comparison evaluator
- Config updates for new features
2026-02-23 19:08:54 -08:00
chopratejas
0adc39ab7a Fix CI: guard starlette imports, asyncio.run(), deprecate datetime.utcnow()
- Guard starlette imports in test_compress_api.py (skip ASGI tests without proxy deps)
- Replace asyncio.get_event_loop().run_until_complete() with asyncio.run() (Python 3.13)
- Replace datetime.utcnow() with datetime.now(timezone.utc).replace(tzinfo=None) everywhere
2026-02-19 11:03:24 -08:00
chopratejas
0a434531d8 Fix: guard starlette imports in test_compress_api.py for CI without proxy deps 2026-02-19 10:48:39 -08:00
chopratejas
dde2f9f848 Add one-function compress() API, ASGI middleware, LiteLLM callback
Three new integration paths — no proxy needed:

1. headroom.compress(messages, model) → CompressResult
   One function, auto-detects tokenizer per model, works with any client.

2. headroom.integrations.asgi.CompressionMiddleware
   Drop-in ASGI middleware for LiteLLM proxy, FastAPI, or any ASGI app.

3. headroom.integrations.litellm_callback.HeadroomCallback
   LiteLLM callback: litellm.callbacks = [HeadroomCallback()]

Fix: TransformPipeline._get_tokenizer() no longer requires a Provider.
Falls back to tokenizer registry which auto-detects per model:
- OpenAI → tiktoken (exact)
- Anthropic → calibrated estimation (3.5 chars/token)
- Open models → HuggingFace (if installed)

15 tests covering compress(), ASGI middleware, LiteLLM callback.
2026-02-19 10:00:31 -08:00
chopratejas
7c2c55abc0 Add Compression Hooks — extension points for SaaS and advanced customization
Three hooks at well-defined pipeline stages:

1. pre_compress(messages, ctx) → messages
   Modify messages before compression: cross-turn dedup, memory injection.

2. compute_biases(messages, ctx) → dict[int, float]
   Per-message compression bias: position-aware, phase-aware, learned.

3. post_compress(event) → None
   Observe results: failure-driven learning, analytics, A/B testing.

- headroom/hooks.py: CompressionHooks, CompressContext, CompressEvent
- ProxyConfig.hooks: optional, default None (zero overhead)
- Wired into Anthropic and OpenAI handlers
- ContentRouter reads hook biases, multiplies with tool bias
- 11 tests
2026-02-19 08:17:30 -08:00
chopratejas
d4d8dd0c26 Add Query Echo: re-inject user question after compressed tool outputs
Query Echo addresses attention decay in compressed contexts. After
SmartCrusher compresses tool outputs, the user's question may be
thousands of tokens away. Echo appends a brief reminder after the
last compressed block.

- New: headroom/transforms/query_echo.py
- Compression-ratio-proportional: only triggers when >30% compressed
- Cache-safe: appended at end (after KV cache boundary)
- Provider-agnostic: Anthropic, OpenAI, Gemini
- 18 tests (15 unit + 3 integration with real API)
- Fix _crush_array 4-tuple unpack in test_critical_fixes.py
2026-02-18 23:39:26 -08:00
chopratejas
729cc035a4 Add compression summaries, multi-provider headers, Dockerfile fix
Compression Summaries:
- New: headroom/transforms/compression_summary.py
  - summarize_dropped_items(): categorizes compressed JSON items by
    field values (status, type, level, etc.), highlights errors/failures
  - summarize_compressed_code(): extracts function names from AST
    signatures (language-agnostic: Python, JS, Go, Rust, Java)
  - Newline-safe: strips \n from field values to keep markers single-line
- SmartCrusher: CCR markers include categorical summary of dropped items
  e.g. "[500 items compressed to 20. Omitted: 87 passed, 2 failed.
  Retrieve more: hash=abc123. Expires in 5m.]"
- CodeCompressor: CCR markers list compressed function names from AST
  e.g. "[180 tokens compressed. 5 bodies compressed: authenticate().
  Retrieve more: hash=abc123. Expires in 5m.]"
- Markers include TTL so LLM knows retrieval window
- Summary escapes { } to prevent .format() crashes
- Uses index-based dropped detection (not id()) for .copy() correctness

Proxy Response Headers:
- Anthropic, OpenAI, and Gemini handlers inject x-headroom-tokens-*
  headers for SaaS metering

Multi-Provider Passthrough Routing:
- Detect x-goog-api-key (Gemini) and api-key (Azure OpenAI)
- X-Headroom-Base-URL for explicit upstream URL override

Dockerfile: add build-essential + g++ for hnswlib compilation
Bump version to 0.3.5

Tests: 27 new tests (unit, eval, integration with real API, tool invocation)
2026-02-18 16:54:20 -08:00
chopratejas
94a6cd8d99 Add pluggable adapter hooks for CCR, Storage, and TOIN backends
Enable SaaS packages to inject custom backends (Redis, PostgreSQL, etc.)
without forking OSS, using entry_points and ContextVars for tenant isolation.

CCR: Request-scoped ContextVar + HEADROOM_CCR_BACKEND env + entry_point loading
Storage: URL-scheme entry_point resolution for custom storage backends
TOIN: New TOINBackend protocol + FileSystemTOINBackend (extracted from toin.py)
      + HEADROOM_TOIN_BACKEND env + entry_point loading

31 tests covering protocol conformance, backend wiring, entry_point loading,
ContextVar thread isolation, and end-to-end adapter lifecycle.
2026-02-15 17:47:30 -08:00
chopratejas
636abd22f3 Add tests for streaming resilience and concurrent session safety
Tests cover model resolution caching (10 tests), streaming error
handling for httpx errors (7 tests), concurrent session safety (4 tests),
and cost tracking accuracy without cache double-counting (3 tests).
2026-02-11 11:24:12 -08:00
chopratejas
8f0754a622 Fix security vulnerabilities in memory and CCR systems
- Fix race condition in BatchContextStore.stats() by acquiring lock
- Add atomic dict snapshot in get_memory_stats() to prevent RuntimeError
- Add metadata key validation to prevent JSON path injection in SQLite
- Parameterize LIMIT/OFFSET in SQLite queries to prevent SQL injection
- Strengthen CCR hash validation to require exactly 24 hex characters
- Add comprehensive security validation tests
2026-02-04 12:05:50 -08:00
chopratejas
3095c73282 Fix MCP tests to work when MCP SDK is not installed
- Mock MCP SDK availability for install/status/lifecycle tests
- Skip MCP server initialization tests when SDK not available
- Skip negative test (test without SDK) when SDK is installed

Tests now pass both with and without MCP SDK installed.
2026-02-02 11:23:26 -08:00
chopratejas
fe2e30a7ef Add MCP CLI for Claude Code subscription users
- Add `headroom mcp install` to configure ~/.claude/mcp.json
- Add `headroom mcp uninstall` to remove configuration
- Add `headroom mcp status` to check setup
- Add `headroom mcp serve` for MCP server (called by Claude Code)
- Add `mcp` optional dependency in pyproject.toml
- Add docs/mcp.md with full documentation
- Add 22 integration tests for MCP CLI
- Fix mypy errors in trained_router.py (None check, type annotation)
- Update README with subscription user instructions

This enables CCR (Compress-Cache-Retrieve) for subscription users who
don't have API access. MCP is Claude's official extension mechanism
that works with subscriptions.

Usage:
  pip install "headroom-ai[mcp]"
  headroom mcp install
  headroom proxy  # Terminal 1
  claude          # Terminal 2
2026-02-02 11:05:24 -08:00
Prakersh Maheshwari
658dc6c06c test: Update LLMLingua default model test to match new default
The default model was changed from xlm-roberta-large (~1GB) to
bert-base-multilingual (~350MB) to reduce memory usage. Updated
the test to reflect this change.

Fixes failing CI test: test_llmlingua_compressor.py::test_default_values

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 20:38:02 +05:30
chopratejas
7cf10675ea Add centralized ML model configuration
- Create headroom/models/config.py as single source of truth for all ML model defaults
- Support environment variable overrides (HEADROOM_SENTENCE_TRANSFORMER, etc.)
- Update all components to use ML_MODEL_DEFAULTS instead of hardcoded values
- Switch LLMLingua default to smaller bert-base model (~350MB vs 1GB)
- Total memory footprint reduced from ~1.6GB to ~980MB

Updated files:
- MLModelRegistry now resolves defaults from config
- EmbeddingScorer, LocalEmbedder, TrainedRouter use config
- All dataclass configs use field(default_factory=...) for consistency
- Tests updated to handle auto-selected vector backends
2026-02-01 23:47:42 -08:00
chopratejas
0bd9ce5024 Fix test_router_is_available_with_models after MLModelRegistry refactor
Remove unnecessary module-level patches for AutoTokenizer and
AutoModelForSequenceClassification since models are now loaded via
MLModelRegistry. The _load_models mock is sufficient for this test.
2026-02-01 22:42:11 -08:00
chopratejas
67d7db87cc Fix test to expect VectorBackend.AUTO as default 2026-02-01 22:25:26 -08:00
chopratejas
eca5c94229 Add SQLite vector backend as default and fix HNSW test skipping
- Add VectorBackend.AUTO that prefers SQLITE_VEC if available, else HNSW
- Add SQLiteVectorIndex configuration options (vector_db_path, vector_cache_size_kb)
- Add update_embedding() method to SQLiteVectorIndex for VectorIndex protocol
- Update factory to create SQLiteVectorIndex when appropriate
- Skip HNSW-specific tests when hnswlib is not available (fixes CI on Python 3.12)
2026-02-01 22:12:45 -08:00
chopratejas
84ad47eba9 Add SQLiteVectorIndex using sqlite-vec for bounded vector search
A SQLite-based alternative to HNSWVectorIndex offering:
- True CRUD operations (real deletes, not marks)
- Bounded memory via SQLite page cache (default 8MB)
- Persistent storage by default
- Cosine similarity search
- Native integration potential with FTS5 for hybrid search

New files:
- headroom/memory/adapters/sqlite_vector.py: Implementation
- tests/test_sqlite_vector_index.py: 16 comprehensive tests

Dependencies:
- sqlite-vec added as optional dependency (pip install sqlite-vec)
- Requires Python built with loadable extension support

Key advantages over HNSWVectorIndex:
- No SIGILL crash risk (pure C, no AVX requirement)
- True deletes (not just marks, space actually reclaimed)
- Simpler persistence (SQLite handles it automatically)
- Consistent with SQLiteGraphStore (same technology stack)
2026-02-01 21:23:09 -08:00
chopratejas
b27c95bdac Add bounded memory support to HNSWVectorIndex with LRU eviction
HNSWVectorIndex now supports optional memory bounding:
- New max_entries parameter sets soft limit on number of entries
- When limit reached, lowest importance entries are evicted
- Eviction uses importance (ascending) then age (oldest first) ordering
- eviction_batch_size controls how many entries evicted at once

Changes:
- Add max_entries and eviction_batch_size parameters
- Add _evict_entries() method for importance-based eviction
- Update get_memory_stats() to report budget_bytes and evictions
- Update stats() to include eviction metrics
- Update save_index/load_index to persist eviction settings
- Add 5 new tests for eviction behavior
2026-02-01 21:12:06 -08:00
chopratejas
f2ecf1d2d0 Add SQLiteGraphStore for bounded, persistent graph storage
Replace unbounded InMemoryGraphStore with SQLite-backed implementation:
- Persistent storage survives proxy restarts
- Memory bounded by configurable SQLite page cache (default 8MB)
- Same async interface as InMemoryGraphStore (drop-in replacement)
- LocalBackend now uses SQLiteGraphStore by default (graph_persist=True)

New files:
- headroom/memory/adapters/sqlite_graph.py: SQLite graph store implementation
- tests/test_sqlite_graph_store.py: 37 comprehensive tests

Key features:
- O(log n) lookups via database indexes
- Case-insensitive entity name lookup per user
- BFS subgraph traversal and shortest path finding
- CASCADE delete for entity relationships
- MemoryTracker integration via get_memory_stats()
2026-02-01 20:48:57 -08:00
chopratejas
e16691dd38 Add memory observability system (Phase 1)
Implements comprehensive memory tracking for all in-memory components:

- Add MemoryTracker singleton with ComponentStats, ProcessStats, MemoryReport
- Add get_memory_stats() to CompressionStore, BatchContextStore,
  GraphStore, HNSWVectorIndex
- Add /debug/memory API endpoint for runtime monitoring

Components tracked:
- compression_store: CCR compressed tool outputs
- batch_context_store: Batch API request contexts
- graph_store: Knowledge graph entities and relationships
- vector_index: HNSW vector embeddings
- semantic_cache: Response cache
- request_logger: Request metadata

Includes 47 tests (unit + integration) with real API calls.
2026-02-01 19:49:53 -08:00
chopratejas
5e2186c42a Add multi-provider memory system with auto-detection
- Add MemoryToolAdapter for unified memory across providers
- Anthropic: Uses native memory tool (memory_20250818) for subscription safety
- OpenAI/Gemini/Others: Uses function calling format
- All providers share the same semantic vector store backend
- Simplify CLI to single --memory flag with auto-detection
- Add proper resource cleanup (close methods) to fix test isolation
- Update README with memory documentation
2026-02-01 14:42:50 -08:00
chopratejas
d7d50fef60 fix: Extend Anthropic format tool protection to IntelligentContextManager
Follows up on PR #19 which fixed RollingWindow but missed IntelligentContextManager,
the default context manager used by the proxy.

Changes:
1. intelligent_context.py: Extended `_get_protected_indices()` to handle Anthropic format:
   - Scan assistant.content for type="tool_use" blocks
   - Protect user messages containing type="tool_result" blocks with matching tool_use_id

2. test_intelligent_context.py: Added TestAnthropicFormatToolProtection class with 5 tests:
   - test_anthropic_tool_result_protected_when_tool_use_protected
   - test_anthropic_tool_units_dropped_atomically
   - test_anthropic_multiple_tools_same_message_atomic
   - test_anthropic_format_no_api_error_scenario (verifies bug fix)
   - test_mixed_openai_and_anthropic_formats

3. test_rolling_window.py: Added matching TestAnthropicFormatToolProtection class with 5 tests
   - Added skip decorator for CI/CD when OPENAI_API_KEY is not set

This ensures both context managers (RollingWindow and IntelligentContextManager) correctly
handle Anthropic's native tool_use/tool_result format, preventing the
"unexpected tool_use_id found in tool_result blocks" API error.
2026-02-01 12:27:17 -08:00
Prakersh Maheshwari
f83f92a51b fix: Handle Anthropic format tool_use/tool_result as atomic units
Root Cause:
The `find_tool_units()` function in `parser.py` only detected OpenAI
format tool calls (assistant.tool_calls + role="tool" messages), not
Anthropic format (assistant.content[type=tool_use] + user.content[type=tool_result]).

This caused RollingWindow and IntelligentContext transforms to treat
Anthropic tool_use and tool_result as separate, independently droppable
messages. When context needed to be trimmed, the assistant message with
tool_use could be dropped while keeping the user message with tool_result,
creating orphaned tool_result blocks.

When sent to the Anthropic API, this produces the error:
"unexpected tool_use_id found in tool_result blocks"

Changes:
1. parser.py: Extended `find_tool_units()` to detect Anthropic format:
   - Scan user messages for content blocks with type="tool_result"
   - Scan assistant messages for content blocks with type="tool_use"
   - Map tool_use_id to corresponding response message indices

2. rolling_window.py: Extended `_get_protected_indices()` to protect
   Anthropic format tool pairs:
   - Detect tool_use blocks in assistant.content
   - Find and protect matching user messages with tool_result blocks

3. tests/test_parser.py: Added 4 new tests for Anthropic format:
   - test_anthropic_format_tool_use_and_result
   - test_anthropic_format_multiple_tool_uses
   - test_anthropic_format_orphaned_tool_result
   - test_mixed_openai_and_anthropic_formats

Test Results: 82 passed (including 4 new Anthropic format tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 00:26:25 +05:30
Claude Code Bot
1c5a0e09fa fix(tests): add missing skip decorator and tests for exclude_tools
## What this PR fixes

1. **CI Python 3.12 failure**: Added skip decorator to `TestLocalBackend`
   in `test_memory_system.py` - these tests require hnswlib which is not
   available on all CI runners.

2. **Missing test coverage**: Added 6 tests for the `exclude_tools` feature
   in `test_content_router.py`. Tests use existing helper functions
   `generate_python_code()`, `generate_json_data()`, and
   `generate_search_results()` defined at lines 57-95 of the same file.

3. **Anthropic/OpenAI inconsistency**: Fixed `_process_content_blocks()`
   to add `router:excluded:tool` marker for Anthropic format, matching
   the OpenAI format behavior at line 1157.

4. **Dead code removal**: Removed unused `exclude_tools` field from
   `SmartCrusherConfig` - the actual implementation uses
   `ContentRouterConfig.exclude_tools` in content_router.py.

AI review: code-reviewer (2 iterations), adversarial-reviewer (2 iterations)
Issues fixed: missing test coverage, format inconsistency, dead code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:44:03 -08:00
Claude Code Bot
b6b8eed3bd fix(tests): skip memory tests when hnswlib not available
Add pytestmark skip conditions to memory test modules that depend on
hnswlib (core_operations, factory, easy). The subprocess probe for
hnswlib correctly detects unavailability on some platforms (like
Python 3.13 CI runners), but these tests were still trying to run
and failing with ImportError.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:09:53 -08:00
Claude Code Bot
9ff67fbd1b fix(tests): skip HTML extractor tests when trafilatura not installed
Add pytest.importorskip("trafilatura") to HTML extractor test modules
to skip tests gracefully when the optional trafilatura dependency is
not installed. This fixes CI failures in the base test matrix that
doesn't include the html extras.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:39:55 -08:00
chopratejas
d1a28322cc Add HTMLExtractor for web content extraction with OSS benchmarks
HTMLExtractor uses trafilatura to extract main content from HTML pages,
removing scripts, styles, navigation, and ads. This achieves 94.9%
compression while preserving 98.2% recall on the Scrapinghub benchmark.

Key features:
- Automatic HTML detection in content router
- Configurable output format (markdown or text)
- Metadata extraction (title, author, date, description)
- Batch extraction support

Evaluation framework:
- OSS benchmark integration (Scrapinghub Article Extraction Benchmark)
- LLM-as-judge evaluation for QA accuracy preservation
- F1 score: 0.919 on 181-sample benchmark (baseline: 0.958)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 10:08:29 -08:00
chopratejas
5c740ea427 Add DiffCompressor and fix hnswlib SIGILL crash on CI
DiffCompressor:
- Parse unified diff format and compress by reducing context lines
- Preserve file headers and all +/- change lines
- Score hunks by relevance (error keywords, query matches)
- Add summary line: [N files, +X -Y lines]
- Expected 30-50% savings on typical git diffs
- Wire into content router for CompressionStrategy.DIFF
- 30 tests covering parsing, compression, edge cases

hnswlib SIGILL fix:
- Move hnswlib import from module level to lazy loading
- hnswlib crashes with SIGILL (Illegal Instruction) on CPUs
  without AVX support, before Python can catch the error
- Now imports only when HNSWVectorIndex is actually used
- HNSW_AVAILABLE is checked lazily via __getattr__

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 08:09:55 -08:00
chopratejas
95e9b39b6d feat: Add AWS Strands Agents SDK integration
## Description

Add Headroom integration with AWS Strands Agents SDK, enabling automatic
context optimization and tool output compression for Strands-based agents.

Fixes #14

## Type of Change

- [x] New feature (non-breaking change that adds functionality)
- [x] Documentation update

## Changes Made

### Core Integration (`headroom/integrations/strands/`)

- **HeadroomHookProvider** - Implements Strands `HookProvider` interface for
  automatic tool output compression via `AfterToolCallEvent`. Compresses
  verbose tool outputs before they enter conversation context.

- **HeadroomStrandsModel** - Model wrapper that extends Strands `Model` base
  class for message-level optimization. Implements all required abstract
  methods: `stream()`, `get_config()`, `update_config()`, `structured_output()`.

- **Provider auto-detection** - Automatically detects appropriate Headroom
  provider (Anthropic, OpenAI, Google) based on wrapped Strands model type.

- **`strands-agents` as optional dependency** - Install with
  `pip install headroom-ai[strands]`

### Testing (`tests/integrations/test_strands/`)

- **Real integration tests (25 tests)** - Use actual AWS Bedrock API calls
  with Claude 3 Haiku. Skip automatically when credentials unavailable.

- **Unit tests (57 tests)** - Mock-based tests for internal logic, edge cases,
  and error handling. No credentials required.

### Demo (`examples/strands_bedrock_demo.py`)

- Interactive demo showcasing both integration patterns
- Visual before/after compression comparison with token savings
- 4 verbose tools (search, logs, database, metrics) demonstrating real savings
- Supports `--hook` and `--model` flags for individual demos

## Testing

All tests verified:

- [x] Unit tests pass (57 tests)
- [x] Integration tests pass (25 tests with real Bedrock API)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom/integrations/strands/`)
- [x] Formatting passes (`ruff format --check`)
- [x] Demo runs successfully with ~50% token savings

## Test Output

```
$ pytest tests/integrations/test_strands/ -v
=================== 82 passed in 90.09s ===================

$ ruff check headroom/integrations/strands/ --ignore E402
All checks passed!

$ mypy headroom/integrations/strands/ --ignore-missing-imports
Success: no issues found
```

## Demo Results

```
╭────────────────────────────────────────────────────────────╮
│              HeadroomHookProvider Results                  │
│────────────────────────────────────────────────────────────│
│ Tokens BEFORE compression: 51,961                          │
│ Tokens AFTER compression:  25,658                          │
│ Tokens SAVED:              26,303 (50.6%)                  │
╰────────────────────────────────────────────────────────────╯
```
2026-01-31 00:31:37 -08:00