Grant the release job contents write permission so GitHub releases can be created, and add the missing docs/overrides directory required by MkDocs deployment.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- publish-pypi: add permissions: id-token: write for OIDC trusted publishing
- publish-npm: add npm run build before npm publish for both packages
- publish-github-packages: add npm run build, use --registry for GPR
- version-sync: add update_openclaw_package_json to sync headroom-ai dep range
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The release workflow now uses a loop-free algorithm:
- pyproject.toml is the canonical source of truth (never committed by workflow)
- Git tags use v{canonical}.{height} format (e.g. v0.5.25.3)
- npm publishes use 3-part semver bumped from canonical
- No commit step eliminates infinite release loops
- paths-ignore reduces unnecessary workflow triggers
Also:
- Add .releaseetadata to .gitignore
- Separate npm_version output for semver-compatible npm publishing
- create-release no longer blocks on publish jobs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests expected the old behavior where _crush_number_array prepended a
summary string into the array. The fix in 14415db moved stats to the
strategy string instead, keeping arrays homogeneously numeric. Update
3 tests to check strategy string instead of array[0].
New --stateless flag (or HEADROOM_STATELESS=true env var) disables all
filesystem writes so the proxy runs purely in-memory. Designed for
containerized, read-only, and load-balanced deployments.
When stateless:
- Request logging: memory-only deque (no JSONL file)
- TOIN patterns: in-memory only (no JSON persistence)
- Memory system: disabled (requires SQLite on disk)
- Traffic learning: disabled (requires filesystem)
- Deploy manifests: graceful degradation on write failure
Graceful degradation improvements (apply even without --stateless):
- RequestLogger: wraps file I/O in try/except, falls back to memory
- save_manifest(): catches OSError instead of crashing on read-only FS
- TOIN: explicit "none" backend for stateless mode
Edge proxies such as Cloudflare Workers inject accept-encoding values
(e.g. gzip, br, zstd) into every outbound request. When Headroom
forwarded these headers unchanged to OpenAI or Anthropic, the upstream
could respond with Brotli-encoded content. Because httpx does not
decompress brotli without the optional brotli package, the raw
compressed bytes reached the JSON parser, causing a UnicodeDecodeError
and a 502 response to the client.
Fix: pop accept-encoding before forwarding so httpx negotiates its own
encoding independently. Applied consistently across all four request
header construction sites:
- openai.py: chat completions handler
- openai.py: Responses API handler
- openai.py: generic passthrough handler
- anthropic.py: main messages handler (CCR continuation already had the strip)
Closes#135
Previously `headroom wrap claude --code-graph` downloaded the binary
and indexed the project, but never registered codebase-memory-mcp as
an MCP server — users had to run `claude mcp add` manually.
Now _setup_code_graph() calls `claude mcp add codebase-memory-mcp`
automatically (idempotent, skips if already registered). The uninstall
path (`headroom mcp uninstall`) also cleans up the registration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Number array compression no longer mixes types (string summary was
prepended to numeric array, violating schema-preserving guarantee).
Statistics now go in the strategy string instead.
- Replace instance-level _current_field_semantics with threading.local()
to prevent cross-thread contamination in concurrent crushes.
- Add lock to module-level _within_compressor lazy init (was unprotected).
- Add _MAX_PROCESS_DEPTH=50 guard to _process_value to prevent
RecursionError on deeply nested JSON.
- Remove dead expression (unused stats.max_val - stats.min_val).
- Fix all UP038 isinstance(x, (A, B)) -> isinstance(x, A | B) across file.
- Add 11 regression tests covering all fixes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ThreadPoolExecutor-based parallel file scanning to all three learn
plugins (Claude, Codex, Gemini). Each plugin's scan_project() now
accepts max_workers and fans out _scan_session() calls concurrently.
New CLI option: headroom learn --workers N (default: min(cpu_count, 8)).
Use --workers 1 for serial behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_memory_sync.py: remove unused imports (asyncio, MagicMock,
AgentMemory, AgentMemoryAdapter, SyncResult), fix import sorting
- test_ws_memory_relay.py: remove unused pytest import and unused
output_index variable, fix import sorting
- test_wrap_copilot.py: provide dummy API keys in test env — the
BYOK validation added in 7a7b8b6 requires ANTHROPIC_API_KEY or
OPENAI_API_KEY to be set
- test_package_init_lazy.py: stop hardcoding version string that
breaks on every bump; assert it's a non-empty string instead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Commit 911eb85 migrated docs from flat .md files to a Fumadocs site
under docs/content/docs/*.mdx but left the README pointing at the old
paths. Update all 30+ links to their new locations, remove rows for
docs that were deleted without replacement (CLI reference, persistent
installs, latency benchmarks), and consolidate Docker-native install
and TypeScript SDK links into their new homes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_component_tokenizations only split on `-` and `.`, so directory names
like `my_project` could never be reconstructed from the dash-encoded
slug. Add `_` as a separator so the greedy decoder matches snake_case
directory names correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot BYOK requires COPILOT_PROVIDER_API_KEY to be set, but the
wrapper only set COPILOT_PROVIDER_TYPE and COPILOT_PROVIDER_BASE_URL.
This caused Copilot to error with "Anthropic API key or bearer token
is required" even when the user had ANTHROPIC_API_KEY in their env.
Fix: auto-propagate ANTHROPIC_API_KEY (or OPENAI_API_KEY for openai
provider type) to COPILOT_PROVIDER_API_KEY. Warn if neither is found.
Fixes#156
The Chat Completions handler executed memory tool calls (memory_save,
memory_search, etc.) but discarded the results — returning the original
response with tool_calls but no tool results. The client saw
"unsupported call" because it never received the tool output.
Fix: after executing memory tools, send a continuation request to the
upstream model with the tool results appended as messages, so the model
produces a final user-facing response.
Fixes#157
KompressConfig now accepts model_id, chunk_words, and score_threshold so
domain-specific models (e.g. kompress-finance with 50-word chunks) can be
used without forking the compressor. Model cache is keyed by model_id,
allowing multiple models to coexist. All defaults match prior behavior.
Also fix mypy errors in memory/sync.py from recent merge.
Memory saved in one agent (Codex, Claude Code, Aider) is now accessible
from any other agent through a shared headroom DB. Three integration layers:
MCP Server (headroom.memory.mcp_server):
- stdio MCP server that Codex/Claude discover natively via config.toml
- memory_search with supersession filtering (only active memories returned)
- memory_save accepts atomic facts array — each fact stored/indexed individually
- Auto-supersession: new facts that match existing ones (≥0.70 similarity)
retire the old entry via the supersedes/superseded_by lineage chain
- ONNX embedder pre-loaded at startup (no cold-start on first query)
- HuggingFace offline mode eliminates network latency on startup
Sync Engine (headroom.memory.sync):
- Bidirectional sync: DB ↔ agent-native memory files
- Pluggable adapters: ClaudeCodeAdapter (frontmatter .md files + MEMORY.md index),
CodexAdapter (AGENTS.md sections)
- Fast no-op: fingerprint comparison skips sync when nothing changed (<5ms)
- Content-hash dedup prevents duplicate memories across agents
- Lineage metadata: source_agent, source_file, content_hash, synced_at
- Anti-echo: memories imported from an agent are not re-exported to that agent
- CLI entry point: python -m headroom.memory.sync --agent claude|codex
Wrap CLI integration:
- `wrap codex --memory`: registers MCP server + AGENTS.md guidance + syncs
Claude memories into DB for MCP search
- `wrap claude --memory`: bidirectional sync at startup (DB ↔ Claude files)
- MCP config re-injected after provider config to survive file rewrite
- Cross-platform: Windows path handling in TOML configs and path sanitization
Proxy improvements:
- Responses API: tool format conversion (Chat Completions → Responses API)
- Responses API: memory tool calls handled with proper continuation
- WebSocket: buffer-then-decide relay suppresses memory tool events from
Codex, executes them transparently, relays only the final answer
- memory_handler: supports Responses API function_call format (call_id,
top-level arguments, output[] extraction)
- HNSW vector index now persists to disk via auto_save + save_path
Tests: 37 new tests covering WS relay event suppression, sync import/export,
bidirectional sync, idempotency, fast no-op, lineage, cross-agent interop
_ensure_proxy() now queries the running proxy's /health config to
detect feature mismatches. When --memory, --learn, or --code-graph
is requested but not enabled on the running proxy, it merges flags
and restarts the proxy with the upgraded configuration.
- Expose memory, learn, code_graph, pid in /health config response
- Add _query_proxy_config() and _kill_proxy_by_pid() helpers
- Refactor _detect_running_proxy_backend() to use _query_proxy_config()
Thread the proxy's --memory flag through _start_proxy, _ensure_proxy,
and _launch_tool so that `headroom wrap <tool> --memory` enables
persistent cross-session memory for claude, copilot, codex, aider,
and cursor.
Implements compress_batch() for issue #151. Compresses N texts with
batched forward passes on GPU and falls back to sequential compress()
on CPU where batching doesn't help.
Measured performance (RTX 3080 Ti, 1000-word / ~6K-char inputs):
GPU (PyTorch + CUDA):
N=1: 2.68x speedup (multi-chunk text batches within single call)
N=5: 2.75x speedup
N=12: 2.49x speedup
CPU (ONNX): fallback to sequential — parity with compress() in loop
ONNX Runtime's CPU execution provider does not parallelize across the
batch dimension for this model architecture; verified across default,
physical-cores-only, and single-thread configurations. The fallback
keeps the API useful while that limitation exists.
Features:
- Per-item target_ratio: scalar applies to all, list allows per-text
- Input order preserved in output
- Passthrough parity with compress() on short texts / errors
- Configurable batch_size (default 32)
Tests: 8 new (TestKompressCompressorBatch), 21 total pass.
Closes#151