## Description Establish one centrally resolved, observable, deterministic, versioned runtime rollout-control mechanism for Headroom. Runtime rollout controls which behaviors an already-built artifact may expose; it does not select or qualify a Headroom release/version. ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Bug fix (non-breaking change that fixes rollout enforcement regressions) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made - Added `RolloutChannel`, `HEADROOM_ROLLOUT_CHANNEL`, `--rollout-channel`, and a versioned immutable `RolloutSnapshot` shared by Python configuration boundaries. - Added schema/policy versions, canonical registry and snapshot SHA-256 identities, per-feature decision reasons, disable precedence, unsafe qualification poisoning, strict CLI validation, and fail-closed environment handling. - Added `headroom rollout status --json`, Python `/stats.rollout`, and Rust `/rollout/status` runtime provenance. - Added equivalent Rust snapshot semantics and shared Python/Rust policy vectors while retaining language-specific feature registries. - Enforced rollout policy at alternate Python server composition roots so `HEADROOM_READ_MATURATION=1` cannot bypass its beta gate. - Preserved typed rollout snapshots across multi-worker serialization with schema, policy, registry, snapshot-digest, type, and feature-name validation. - Made loopback runtime output-shaper updates replace the immutable snapshot atomically for request readers, retain explicit request/disable provenance, preserve channel and kill-switch precedence, invalidate cached stats, and return the effective rollout decision. - Made `headroom learn --verbosity --apply` report a channel-blocked update instead of claiming the shaper is live. - Made explicit CLI feature flags fail loudly when their current channel blocks them. - Made persistent interceptor installation select canary automatically, or reject an explicitly insufficient channel unless the break-glass override is set. - Updated architecture, proxy, rollout, learn, and output-shaper documentation with required channels and hot-reload semantics. ## Testing - [x] Unit tests pass - [x] Linting passes (`ruff check .` and `ruff format --check .`) - [x] Type checking passes (`mypy headroom --ignore-missing-imports`) - [x] New regression tests added for every corrected behavior - [x] Rust tests and production-target Clippy pass - [x] Documentation build passes ### Test Output ```text Focused rollout coverage suite 57 passed; headroom.rollout + rollout CLI: 98% coverage Affected proxy/rollout/transform/governance suites 222 passed; 0 failed Final changed regression suites 100 passed; 0 failed Cross-module hot-reload isolation regression 6 passed; 0 failed cargo test -p headroom-core -p headroom-proxy --quiet headroom-core: 924 passed; 1 ignored headroom-proxy and integration suites: all passed cargo clippy -p headroom-core -p headroom-proxy --lib --bins -- -D warnings cargo fmt --all -- --check ruff check . ruff format --check . mypy headroom --ignore-missing-imports git diff --check All passed cd docs && npm run build Compiled successfully; 164 static pages generated ``` The unsharded Windows-only CI selection exposed unrelated baseline failures, principally the existing `sqlite:///C:\\...` URL parser producing an invalid `\\C:\\...` path. At commit `8e793a80`, all 52 completed GitHub checks passed; the only other conclusions are expected skips and superseded governance jobs. ## Real Behavior Proof - **Environment:** Windows checkout on Python 3.13.3 and the current Rust workspace, based on upstream `main` at `93f2d7a2`. - **Exact command / steps:** Exercised canary and beta feature requests through CLI status, Python `/stats.rollout`, Rust `/rollout/status`, multi-worker payload round trips, loopback `/admin/runtime-env`, real proxy request shaping before/after hot reload, installer manifest generation, and shared Python/Rust policy vectors. - **Observed result:** Stable blocks unstable requests; disable wins over explicit/default/legacy/unsafe paths; unsafe state reports `qualification_eligible=false`; worker handoff rejects tampering; running output shaping changes only when the effective beta policy permits it; explicit blocked flags fail with actionable diagnostics. - **Not tested:** Live production traffic requiring provider credentials, or future artifact qualification/promotion automation (intentionally out of scope). ## Runtime Rollout Safety - **Rollout-managed features:** Python `tool_result_interceptors`, `proxy_output_shaper`, `read_maturation`; Rust `native_bedrock`, `openai_responses_streaming`, `canary_probe`. - **Minimum rollout channel:** Registry-defined per feature; process default is `stable`. - **Stable/default behavior changed:** No unstable feature becomes enabled by default. Explicit blocked CLI flags now fail instead of silently doing nothing. - **Kill switch / disable path:** `HEADROOM_DISABLE_FEATURES=<comma-separated feature names>`; explicit disable has highest precedence, including over the unsafe override. - **Unsafe override required:** No. `HEADROOM_UNSAFE_ALLOW_UNSTABLE_FEATURES=1` is break-glass only and makes qualification evidence ineligible. - **Qualification impact:** Adds machine-readable policy/snapshot identities and eligibility; does not implement qualification itself. - **Rollback path:** Set the named disable list for operational rollback, lower the channel, or revert this PR. ## Review Readiness - [x] I have performed a full diff review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented hard-to-understand areas - [x] I have made corresponding documentation changes - [x] My changes generate no new warnings - [x] I added tests that reproduce and prevent every regression fixed during review - [x] New and existing affected tests pass locally - [x] I did **not** edit `CHANGELOG.md`; release-please generates it from the Conventional Commit PR title ## Additional Notes Out of scope: artifact candidates, benchmark orchestration, qualification manifests/gates, promotion automation, release branches, publication guards, and release-risk classification. Those workflows can consume the rollout registry digest, runtime snapshot digest, decision reasons, and qualification eligibility through supported black-box interfaces. --------- Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net> Co-authored-by: JD Davis <jd@JDH-AIR-00.local>
8.9 KiB
Headroom Learn
Offline failure learning for coding agents. Analyzes past conversations, finds what went wrong, correlates it with what eventually worked, and writes specific project-level learnings that prevent the same mistakes next session.
Quick Start
# See recommendations for current project (dry-run, no changes)
headroom learn
# Write recommendations to CLAUDE.local.md (gitignored, personal default)
headroom learn --apply
# Write to the shared team file instead
headroom learn --apply --target CLAUDE.md
# Analyze a specific project
headroom learn --project ~/my-project --apply
# Analyze all projects
headroom learn --all --apply
How It Works
Past Sessions → Plugin → Analyzer → Writer → Agent-native context file
│ │ │
│ │ └─ Writes marker-delimited sections
│ │ (replaced on re-run, not duplicated)
│ │
│ └─ LLM-based analysis: finds failure patterns,
│ success correlations, and actionable rules
│
└─ Plugin reads agent-specific logs:
• Claude Code: ~/.claude/projects/*.jsonl
• Codex: ~/.codex/sessions/*.json
• Gemini CLI: ~/.gemini/tmp/*/chats/session-*.json
Success Correlation
The core innovation. Instead of cataloging failures ("Read failed 5 times"), Headroom finds what the model did to fix each failure:
- Failed:
Read axion-formats/src/main/java/.../FirstClassEntity.java - Then succeeded:
Read axion-scala-common/src/main/scala/.../FirstClassEntity.scala - Learning: "
FirstClassEntityis ataxion-scala-common/, notaxion-formats/"
This produces specific, actionable corrections — not generic advice.
What It Learns
1. Environment Facts → CLAUDE.md
Which runtime commands work vs fail.
### Environment
- **Python**: use `uv run python` (not `python3` — modules not available outside venv)
2. File Path Corrections → CLAUDE.md
Wrong paths the model keeps guessing, with the correct locations.
### File Path Corrections
- `axion-common/src/.../AxionSparkConstants.scala`
→ actually at `axion-spark-common/src/.../AxionSparkConstants.scala`
3. Search Scope → CLAUDE.md
Which directories to search in (narrow paths fail, broader ones work).
### Search Scope
- Don't search `axion-model/` → use `axion/` (the repo root)
4. Command Patterns → CLAUDE.md
How commands should (and shouldn't) be run.
### Command Patterns
- **user_prefers_manual**: User rejected gradle 18 times — show the command, don't execute
- **python_runtime**: Use `uv run python` not `python3` (ModuleNotFoundError)
5. Known Large Files → CLAUDE.md
Files that need offset/limit with Read.
### Known Large Files
- `proxy/server.py` (~8000 lines) — always use offset/limit
6. Retry Prevention → MEMORY.md
Specific suggestions derived from actual corrections.
7. Permission Notes → MEMORY.md
Commands repeatedly rejected — model should suggest them to the user instead.
Where Learnings Go
| Pattern | Claude Code | Codex | Gemini CLI |
|---|---|---|---|
| Environment, paths, commands | CLAUDE.local.md (default) or CLAUDE.md (with --target CLAUDE.md) |
AGENTS.md | GEMINI.md |
| Retry patterns, permissions | MEMORY.md | instructions.md | GEMINI.md |
Output files are agent-native: Claude Code writes to CLAUDE.local.md by default (gitignored, personal); pass --target CLAUDE.md for the shared team file. Codex uses AGENTS.md, Gemini uses GEMINI.md. The same learnings, written to the format each agent reads.
Marker-Based Updates
Headroom manages a clearly-delimited section in each file:
<!-- headroom:learn:start -->
## Headroom Learned Patterns
*Auto-generated by `headroom learn` — do not edit manually*
...
<!-- headroom:learn:end -->
On re-run, only the content between markers is replaced. Your existing file content is preserved.
Architecture (Plugin System)
Headroom Learn uses a plugin architecture where each agent is a self-contained plugin:
Plugin Registry (auto-discovered)
├── ClaudeCodePlugin → Analyzer (LLM) → ClaudeCodeWriter → CLAUDE.md / MEMORY.md
├── CodexPlugin → Analyzer (LLM) → CodexWriter → AGENTS.md / instructions.md
├── GeminiPlugin → Analyzer (LLM) → GeminiWriter → GEMINI.md
└── (your plugin) → Analyzer (LLM) → (your writer) → (your file)
Plugins bundle scanning, detection, and writing for one agent. Built-in plugins are auto-discovered from headroom.learn.plugins.*. External plugins register via the headroom.learn_plugin entry point.
The Analyzer is shared — it uses an LLM (Sonnet, GPT-4o, or Gemini Flash) to find patterns. Same analysis for any agent.
Adding Support for a New Agent
- Create
headroom/learn/plugins/myagent.py - Implement
LearnPlugin+ConversationScanner(scanner + writer + detection) - Add
plugin = MyAgentPlugin()at module scope - Done —
headroom learn --agent myagentworks automatically
Or install an external plugin: pip install headroom-learn-cursor (registers via entry point).
CLI Reference
headroom learn [OPTIONS]
Options:
--project PATH Project directory (default: current directory)
--all Analyze all discovered projects (mutually exclusive with --project)
--apply Write recommendations (default: dry-run)
--target TEXT Context file to write (default: CLAUDE.local.md for Claude Code)
--main-only Write only to the main context file, skip MEMORY.md
--agent [auto|claude|codex|gemini]
Which agent to analyze (default: auto-detect)
--model TEXT LLM for analysis (default: auto from API keys or CLI)
--workers / -j INTEGER Parallel analysis workers (min 1, default: auto)
--verbosity Analyze verbosity level instead of failure patterns
--llm-judge Use an LLM to score verbosity quality (requires --verbosity)
Verbosity learning (--verbosity)
headroom learn --verbosity analyzes past sessions to infer the ideal output verbosity level for your project and writes a verbosity.json profile.
Important: the output shaper is off by default and requires the beta
runtime rollout channel. Running --verbosity --apply will either:
- Hot-enable the output shaper on an eligible running proxy (
POST /admin/runtime-env), OR - Print instructions to set
HEADROOM_ROLLOUT_CHANNEL=betaandHEADROOM_OUTPUT_SHAPER=1beforeheadroom wrap ...
To keep the shaper on across proxy restarts, export both variables before starting the proxy.
Flag interactions:
--alland--projectare mutually exclusive--llm-judgerequires--verbosity--verbosity --all --applyis rejected (verbosity persists a single global level)
Supported Agents
| Agent | Scanner | Writer | Output Files |
|---|---|---|---|
| Claude Code | Reads ~/.claude/projects/*.jsonl |
ClaudeCodeWriter | CLAUDE.md, MEMORY.md |
| OpenAI Codex | Reads ~/.codex/sessions/*.json |
CodexWriter | AGENTS.md, instructions.md |
| Gemini CLI | Reads ~/.gemini/tmp/*/chats/session-*.json |
GeminiWriter | GEMINI.md |
LLM Backend Selection
headroom learn needs an LLM to analyze your sessions. It picks one automatically using this priority:
| Priority | Source | Example |
|---|---|---|
| 1 | --model flag |
headroom learn --model gpt-4o |
| 2 | API key env var | ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY |
| 3 | HEADROOM_LEARN_CLI env var |
export HEADROOM_LEARN_CLI=gemini |
| 4 | Auto-detect installed CLIs | Checks PATH for claude, gemini, codex |
Using without an API key
If you use Claude Code, Gemini CLI, or Codex via subscription (no raw API key), headroom learn can call them directly:
# Auto-detects claude in PATH — no API key needed
headroom learn
# Explicitly select a CLI backend
headroom learn --model gemini-cli
# Pin a CLI via environment variable
export HEADROOM_LEARN_CLI=codex
headroom learn
Valid values for HEADROOM_LEARN_CLI: claude, gemini, codex.
Real-World Results
Tested on 67,583 tool calls across 23 projects:
| Metric | Value |
|---|---|
| Failure rate | 7.5% (5,066 failures) |
| Corrections extracted | 164 per project (avg) |
| Specific path corrections | 22 (axion project) |
| Search scope corrections | 24 (axion project) |
| Command patterns learned | 5 (axion project) |
| Estimated preventable waste | ~27 MB across corpus |