Skip to content

Instantly share code, notes, and snippets.

@prakhar1989
Created June 23, 2026 06:01
Show Gist options
  • Select an option

  • Save prakhar1989/f9a0d51038da0dd0c2c874fb141b7b35 to your computer and use it in GitHub Desktop.

Select an option

Save prakhar1989/f9a0d51038da0dd0c2c874fb141b7b35 to your computer and use it in GitHub Desktop.
Headroom monorepo split plan

Headroom Monorepo Split Plan

Analysis and implementation plan for splitting the headroom monorepo into separate repositories for easier code management, code review, and PR workflows.


Table of Contents


Current State

What the monorepo contains

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

Shared infrastructure

  • Rust workspace — all 4 crates (core, proxy, py, parity) share a single Cargo.toml with workspace-level dependencies and a pinned rust-toolchain (1.95.0)
  • Version locksteprelease-please manages a single version (0.24.0) and syncs it to pyproject.toml, sdk/typescript/package.json, plugins/openclaw/package.json, and agent hooks manifests via scripts/version-sync.py
  • Single release pipelinerelease.yml builds wheels, npm packages, Docker images, and publishes everything in one workflow
  • Shared MakefileMakefile at root drives all Rust/Python/test targets
  • Shared CI — 12 GitHub Actions workflows, many of which cross component boundaries (e.g., ci.yml builds Rust extension + Python tests + Docker e2e)

Dependency Topology

                    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.


Proposed Split: Option A (recommended)

Split into up to 5 repos, extracting headroom-core as a standalone published crate.

Repo 1: headroom-core (standalone Rust 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.

Repo 2: headroom (Python package)

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.

Repo 3: headroom-proxy (Rust proxy + Docker)

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.

Repo 4: headroom-sdk-typescript (npm SDK)

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.

Repo 5: headroom-plugins (plugin monorepo)

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).

Repo 6: headroom-docs (documentation)

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.

Dependency graph after split

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

Migration Plan

Phase 1: Low-hanging fruit (1-2 days)

Split the fully independent components with zero cross-repo dependencies.

  1. Create headroom-sdk-typescript repo

    • 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
  2. Create headroom-docs repo

    • Copy docs/ + wiki/ + mkdocs.yml to new repo root
    • Add GitHub Pages deploy workflow
    • Remove from monorepo
  3. Create headroom-plugins repo

    • Copy plugins/ directory to new repo root
    • Add CI + publish workflow
    • Remove plugins/ from monorepo
    • Remove version-sync entries for plugin manifests

Phase 2: Extract headroom-core (2-3 days)

  1. Create headroom-core repo

    • Copy crates/headroom-core/ + rust-toolchain.toml to 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
  2. Update headroom monorepo

    • Change crates/headroom-core path dep → crates.io dep in crates/headroom-py/Cargo.toml and crates/headroom-parity/Cargo.toml
    • Remove headroom-core from workspace members
    • Verify maturin build and cargo test still work
  3. Update headroom-proxy (in preparation for Phase 3)

    • Same dep change in crates/headroom-proxy/Cargo.toml

Phase 3: Extract headroom-proxy (1-2 days)

  1. Create headroom-proxy repo

    • Copy crates/headroom-proxy/ + Dockerfile + docker-bake.hcl + docker-compose.yml + docker/ + .dockerignore
    • Create standalone Cargo.toml workspace with headroom-core as crates.io dep
    • Set up CI: cargo test, Docker build + push
    • Set up GHCR publish workflow
  2. Remove proxy from monorepo

    • Delete crates/headroom-proxy/
    • Remove Docker files related to proxy
    • Remove proxy-related CI jobs

Phase 4: Clean up (1 day)

  1. Clean up monorepo
    • Remove scripts/version-sync.py and scripts/verify-versions.py (or simplify for single-package use)
    • Remove release-please config
    • Simplify Makefile — remove proxy/parity targets
    • Simplify CI — remove workflows that belong to other repos
    • Update README.md with links to new repos
    • Update CONTRIBUTING.md

Key Risks & Considerations

1. The headroom-core shared dependency

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" }

2. Development workflow friction

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_dispatch or repository_dispatch

3. The "Python retirement" plan (Phase H)

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.

4. Release versioning divergence

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/sdk to decouple from the PyPI package name
  • Document which versions work together in release notes

5. E2E tests

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-e2e repo.
  • Workflow dispatch: The monorepo triggers E2E suites in other repos via workflow_dispatch after publish.
  • Keep a thin "integration" monorepo: A small repo with just the E2E tests that consumes all published artifacts.

6. CI cache duplication

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.

7. Renaming the npm package

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".


Quick Wins (do first)

  1. Split headroom-sdk-typescript — zero dependencies, immediate benefit
  2. Split headroom-docs — docs changes stop triggering Python/Rust CI
  3. 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.


Summary

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment