Analysis and implementation plan for splitting the headroom monorepo into separate repositories for easier code management, code review, and PR workflows.
- Current State
- Dependency Topology
- Proposed Split: Option A (recommended)
- Migration Plan
- Key Risks & Considerations
- Quick Wins (do first)
| Artifact | Path | Registry | Published As |
|---|---|---|---|
| Python package | headroom/ + crates/headroom-py/ |
PyPI | headroom-ai |
| Rust proxy | crates/headroom-proxy/ |
GHCR | Docker images |
| Rust core lib | crates/headroom-core/ |
crates.io target | library crate |
| Rust parity harness | crates/headroom-parity/ |
(internal tool) | — |
| TypeScript SDK | sdk/typescript/ |
npm | headroom-ai |
| OpenClaw plugin | plugins/openclaw/ |
npm | headroom-openclaw |
| Agent hooks plugin | plugins/headroom-agent-hooks/ |
marketplace | agent manifest |
| Documentation (Fumadocs) | docs/ |
GitHub Pages | static site |
| Documentation (MkDocs wiki) | wiki/ |
GitHub Pages | static site |
| E2E tests | e2e/ |
— | shared test harness |
| Build/CI scripts | scripts/ |
— | shared tooling |
- Rust workspace — all 4 crates (
core,proxy,py,parity) share a singleCargo.tomlwith workspace-level dependencies and a pinned rust-toolchain (1.95.0) - Version lockstep —
release-pleasemanages a single version (0.24.0) and syncs it topyproject.toml,sdk/typescript/package.json,plugins/openclaw/package.json, and agent hooks manifests viascripts/version-sync.py - Single release pipeline —
release.ymlbuilds wheels, npm packages, Docker images, and publishes everything in one workflow - Shared Makefile —
Makefileat root drives all Rust/Python/test targets - Shared CI — 12 GitHub Actions workflows, many of which cross component boundaries (e.g.,
ci.ymlbuilds Rust extension + Python tests + Docker e2e)
headroom-core (Rust)
↙ ↓ ↘
headroom-py headroom-proxy headroom-parity
↓
Python package (headroom/ + pyproject.toml)
↓
PyPI release (headroom-ai)
TS SDK (sdk/typescript/) — fully independent (no Rust/Python dep)
OpenClaw (plugins/openclaw/) — depends on TS SDK (npm dep)
Agent hooks (plugins/headroom-agent-hooks/) — standalone
Docs (docs/ + wiki/) — fully independent
The critical bottleneck is headroom-core: it is a path dependency of both headroom-py (which builds the Python wheel) and headroom-proxy (the Docker proxy binary). You cannot split these cleanly without solving the shared-crate problem.
Split into up to 5 repos, extracting headroom-core as a standalone published crate.
| Field | Value |
|---|---|
| Path | crates/headroom-core/ |
| Registry | crates.io |
| Published as | headroom-core |
| Version | Independent semver |
| CI | cargo test, cargo clippy, cargo publish |
The foundation crate. Published to crates.io and consumed by both the headroom (Python) and headroom-proxy repos.
| Field | Value |
|---|---|
| New path | headroom/, crates/headroom-py/, crates/headroom-parity/ |
| Registry | PyPI |
| Published as | headroom-ai |
| Rust dep | headroom-core from crates.io |
| CI | Python lint, maturin wheel build, pytest, smoke import |
| Release | release-please → PyPI |
This is the main product — what users get when they pip install headroom-ai. Keeps the Python source, PyO3 bindings, parity harness, tests, and Python-specific scripts.
| Field | Value |
|---|---|
| New path | crates/headroom-proxy/ |
| Registry | GHCR |
| Published as | Docker images (ghcr.io/...) |
| Rust dep | headroom-core from crates.io |
| CI | cargo test, Docker build + push |
| Release | Independent semver, Docker to GHCR |
Standalone proxy service with its own Docker release cycle. Includes Dockerfile, docker-compose.yml, docker/, docker-bake.hcl.
| Field | Value |
|---|---|
| New path | sdk/typescript/ (top-level in new repo) |
| Registry | npm |
| Published as | @headroom-ai/sdk (rename to avoid PyPI name collision) |
| CI | vitest, tsc, tsup build |
| Release | Independent semver, npm publish |
Fully independent. No Rust or Python dependency. Separate PRs, reviews, and releases.
| Field | Value |
|---|---|
| New path | plugins/ (each plugin subdirectory) |
| Registry | npm / marketplace |
| Published as | headroom-openclaw, etc. |
| CI | vitest, tsup build |
| Release | Independent per plugin |
Can be a single monorepo for all plugins, or split per-plugin. Depends on the TS SDK (already published independently).
| Field | Value |
|---|---|
| New path | docs/ + wiki/ |
| Deploy | GitHub Pages |
| CI | MkDocs build + deploy, Fumadocs build + deploy |
| Release | Merge-to-main → auto-deploy |
Documentation-only changes should never trigger Python/Rust CI.
headroom-core (crates.io)
↙ ↘
headroom headroom-proxy
(PyPI wheel) (Docker/GHCR)
headroom-sdk-typescript (npm) — fully independent
headroom-plugins (npm) — depends on TS SDK only
headroom-docs (GitHub Pages) — fully independent
Split the fully independent components with zero cross-repo dependencies.
-
Create
headroom-sdk-typescriptrepo- Copy
sdk/typescript/contents to new repo root - Add own
README.md,.gitignore, CI workflow - Remove
sdk/typescript/from monorepo - Remove version-sync entries for
sdk/typescript/package.json
- Copy
-
Create
headroom-docsrepo- Copy
docs/+wiki/+mkdocs.ymlto new repo root - Add GitHub Pages deploy workflow
- Remove from monorepo
- Copy
-
Create
headroom-pluginsrepo- Copy
plugins/directory to new repo root - Add CI + publish workflow
- Remove
plugins/from monorepo - Remove version-sync entries for plugin manifests
- Copy
-
Create
headroom-corerepo- Copy
crates/headroom-core/+rust-toolchain.tomlto new repo - Create standalone
Cargo.toml(no workspace) - Set up CI:
cargo test,cargo fmt --check,cargo clippy,cargo audit - Publish v0.1.0 to crates.io
- Copy
-
Update
headroommonorepo- Change
crates/headroom-corepath dep → crates.io dep incrates/headroom-py/Cargo.tomlandcrates/headroom-parity/Cargo.toml - Remove
headroom-corefrom workspace members - Verify
maturin buildandcargo teststill work
- Change
-
Update
headroom-proxy(in preparation for Phase 3)- Same dep change in
crates/headroom-proxy/Cargo.toml
- Same dep change in
-
Create
headroom-proxyrepo- Copy
crates/headroom-proxy/+Dockerfile+docker-bake.hcl+docker-compose.yml+docker/+.dockerignore - Create standalone
Cargo.tomlworkspace withheadroom-coreas crates.io dep - Set up CI:
cargo test, Docker build + push - Set up GHCR publish workflow
- Copy
-
Remove proxy from monorepo
- Delete
crates/headroom-proxy/ - Remove Docker files related to proxy
- Remove proxy-related CI jobs
- Delete
- Clean up monorepo
- Remove
scripts/version-sync.pyandscripts/verify-versions.py(or simplify for single-package use) - Remove
release-pleaseconfig - Simplify
Makefile— remove proxy/parity targets - Simplify CI — remove workflows that belong to other repos
- Update
README.mdwith links to new repos - Update
CONTRIBUTING.md
- Remove
Risk: headroom-proxy and headroom-py both need headroom-core. If they import different versions, behavior diverges.
Mitigation:
- Start with git-based dependency pinning (
headroom-core = { git = "...", rev = "<sha>" }) - Graduate to crates.io versions once the core API stabilizes
- During active cross-repo development, use local
[patch]overrides in.cargo/config.toml:
[patch.crates-io]
headroom-core = { path = "../headroom-core" }Risk: Coordinated changes across repos need multiple PRs. A bug fix touching headroom-core + headroom-py requires PRs in two repos.
Mitigation:
- Use GitHub's "stacked PRs" workflow (gh CLI
gh pr create --fill --base <branch>) - The
[patch]mechanism above lets local dev work with a single checkout - Automated cross-repo CI can be wired via
workflow_dispatchorrepository_dispatch
Risk: REALIGNMENT/10-phase-H-python-retirement.md plans to eventually replace Python with pure Rust. Splitting now may be premature if Python is retired within 6-12 months.
Recommendation: Split the independent pieces (TS SDK, docs, plugins) now. Keep the Python + Rust monorepo until the Rust rewrite matures. Only extract headroom-core and headroom-proxy when the Rust architecture is more stable.
Risk: Currently everything ships at 0.24.0. After splitting, each repo has its own version. The npm package and PyPI package could both be at different versions, confusing users.
Mitigation:
- Rename the npm package to
@headroom-ai/sdkto decouple from the PyPI package name - Document which versions work together in release notes
Risk: Many e2e/ tests span multiple components (Python install → proxy startup → TS SDK call).
Options:
- Per-repo E2Es: Each repo tests only its own component. Cross-repo scenarios live in a dedicated
headroom-e2erepo. - Workflow dispatch: The monorepo triggers E2E suites in other repos via
workflow_dispatchafter publish. - Keep a thin "integration" monorepo: A small repo with just the E2E tests that consumes all published artifacts.
Risk: Each repo independently caches Rust/Python/Node artifacts, increasing total CI storage.
Cost: Minor — Rust target/ and Python .venv/ are per-repo. The total is 2-3x the current single cache, but GitHub Actions caching is free for public repos.
The current npm package headroom-ai collides with the PyPI headroom-ai name. Recommend renaming the npm package to @headroom-ai/sdk when splitting:
{
"name": "@headroom-ai/sdk",
"publishConfig": {
"access": "public"
}
}This also requires updating the openclaw plugin's dependency from "headroom-ai": "^0.22.3" to "@headroom-ai/sdk": "^1.0.0".
- Split
headroom-sdk-typescript— zero dependencies, immediate benefit - Split
headroom-docs— docs changes stop triggering Python/Rust CI - Split
headroom-plugins— plugin releases become independent
These three give immediate value with minimal risk. They represent things that rarely need coordinated changes with the Python/Rust code and can be done in an afternoon each.
| Component | Priority | Effort | Risk | Dependencies |
|---|---|---|---|---|
| TS SDK | High | 1 day | None | None |
| Docs | High | 0.5 day | None | None |
| Plugins | High | 0.5 day | Low | TS SDK (npm) |
| headroom-core | Medium | 2-3 days | Medium | Published to crates.io |
| headroom-proxy | Medium | 1-2 days | Medium | headroom-core |
| Python monorepo cleanup | Medium | 1 day | Medium | Everything above |