Skip to content

threecorp/bough

Repository files navigation

bough

Per-worktree isolation orchestrator for monorepos.

bough brings up an isolated dev environment per git worktree (per feature branch): a deterministically-allocated port set, an auto-generated .env.local in every sub-repo, and a worktree-local instance of every declared engine — all driven by one .bough.yaml at the monorepo root.

bough itself is a small Go CLI plus five engine plugins (bough-plugin-{mysql,postgres,redis,elasticsearch,compose}), wired together via Hashicorp go-plugin (gRPC over Unix socket). Each of the first four defers the actual lifecycle (up / ready check / down) to a backend you choose: today that's services-flake on top of Nix or a direct Docker SDK backend; the host's auto-detect picks one based on what's on the runner. compose is different by design — instead of provisioning its own engine, it wraps an EXISTING docker-compose.yml/service an operator already has, giving it only worktree-scoped port isolation (see Compose-wrapped services below).

The "what to isolate" is fully declarative — pick which repositories appear under worktrees/<name>/ and which engines spawn per worktree via a single YAML at the monorepo root. Engines are loaded as gRPC plugins, so adding a new engine (rabbitmq, kafka, nats, minio, …) never requires editing the host binary.

Cost & billing

As of 2026-06-27, with Claude Code's current subscription model, bough runs entirely inside your existing Claude Code subscription — it makes no separate Anthropic API call and incurs no separate API billing. How that holds up:

  • The worktree-isolation core (bough create, the engine plugins, .env.local rendering) makes zero LLM calls — it is pure local infrastructure (git, ports, Nix/Docker).
  • The continuous-learning feature (observe → evolve → inject) reaches an LLM only by spawning claude --print as a subprocess, which reuses your operator subscription auth (~/.claude.json oauth token). bough strips ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL / Bedrock / Vertex / CLAUDE_API_KEY from the subprocess env, so it cannot silently flip to API-key billing. There is no Anthropic SDK, and no HTTP client to any model endpoint, anywhere in the binary.
  • The hooks Claude Code fires automaticallyPreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionEnd, PreCompact — are pure filesystem: they append an observation line and (for UserPromptSubmit) print a small instinct block to stdout. They make no LLM call. The UserPromptSubmit block is folded into your next turn as ordinary input tokens of your own session — the same as any context — not a separate charge.
  • LLM calls happen only in the explicit commands bough instinct observer run-once and bough instinct evolve --generate (and the opt-in bough instinct observer start daemon) — each one claude --print under your subscription, hard rate-limited (10 / session, 30 / hour, 3-failure circuit breaker).

Run bough claude doctor to confirm the posture (it warns if an ANTHROPIC_API_KEY-style variable in your shell would override subscription auth).

⚠️ This reflects Claude Code's billing model as of 2026-06-27. bough relies on claude --print subprocess invocations being covered by the Claude Code subscription. If Anthropic changes how Claude Code meters --print / subscription usage, this could change — that is outside bough's control, so verify against current Claude Code pricing if it matters to you.

Prerequisites

bough binaries themselves are static Go executables (darwin / linux, arm64 / amd64) — bough never installs Nix or Docker for you. The host auto-detects which backend each plugin uses with a v0.1.x-compat preference for nix (so monorepos that adopted bough when nix was the only option do not silently flip to docker on upgrade):

  1. nix-with-flakes on PATHnix
  2. else docker daemon reachable → docker
  3. else: actionable error pointing at the engines[].backend YAML knob

In practice that means nix users with flakes enabled (= those who already installed Nix and turned on the nix-command/flakes experimental features before adopting bough) get nix; everyone else (the typical install, including a bare Nix install without flakes) gets docker. An explicit backend: nix | docker per engine in .bough.yaml always overrides auto-detect.

Backend When auto-detect picks it User must provide
docker nix-with-flakes not on PATH, docker daemon reachable (= typical install) A Docker-compatible daemon (Docker Desktop / OrbStack / Colima / podman with the docker socket)
nix nix-with-flakes on PATH Nix with flakes enabled + network access to flakehub.com / github.com on first invocation

Cold-start cost (first bough create invocation on a fresh machine):

Backend Cold start Warm start
nix (v0.1.0, no bundled flake.lock; historical) 5-10 min ⚠ 10-60 s
nix (v0.1.1+, bundled flake.lock) 30-60 s 5-10 s
docker (v0.2+, after image pull) image pull dominant 1-5 s

v0.1.1 added the bundled flake.lock per plugin (no more flakehub.com round-trip on every fresh worktree); v0.2 added the Docker backend so users who prefer Docker over Nix can avoid Nix entirely.

Install

bough ships as 6 binaries (bough + 5 bough-plugin-*). Pick one:

# 1. GitHub Release tarball (recommended; no Go / Nix toolchain needed)
#    Available for darwin/linux × arm64/amd64. Two translations are
#    needed to build the asset URL: `uname -m` reports x86_64/aarch64
#    but assets are named by Go's GOARCH (amd64/arm64), and the asset
#    filename embeds the release version (bough_<ver>_<os>_<arch>) so
#    the tag is resolved first via the /releases/latest redirect.
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac
tag=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/threecorp/bough/releases/latest); tag=${tag##*/}
curl -fsSL "https://github.com/threecorp/bough/releases/download/${tag}/bough_${tag#v}_$(uname -s | tr A-Z a-z)_${arch}.tar.gz" \
  | tar xz -C ~/.local/bin/  bough bough-plugin-mysql bough-plugin-postgres bough-plugin-redis bough-plugin-elasticsearch bough-plugin-compose
#
# macOS (Apple Silicon) one-time step: the release binaries are not
# notarized, so Gatekeeper kills them on first run ("zsh: killed").
# Ad-hoc re-sign locally once after install (and clear quarantine if
# you downloaded via a browser):
#   xattr -dr com.apple.quarantine ~/.local/bin/bough ~/.local/bin/bough-plugin-* 2>/dev/null
#   codesign --force --sign - ~/.local/bin/bough ~/.local/bin/bough-plugin-*

# 2. go install (per-binary; requires Go toolchain on PATH)
go install github.com/ikeikeikeike/bough/cmd/bough@latest
go install github.com/ikeikeikeike/bough/cmd/bough-plugin-mysql@latest
go install github.com/ikeikeikeike/bough/cmd/bough-plugin-postgres@latest
go install github.com/ikeikeikeike/bough/cmd/bough-plugin-redis@latest
go install github.com/ikeikeikeike/bough/cmd/bough-plugin-elasticsearch@latest
go install github.com/ikeikeikeike/bough/cmd/bough-plugin-compose@latest

# 3. Nix flake (requires Nix with flakes enabled)
nix run    github:threecorp/bough -- create --stdin-json
nix profile install github:threecorp/bough

# 4. Homebrew (planned — tap not yet published)
# brew tap     threecorp/tap
# brew install bough

Use from Claude Code (plugin)

bough is also packaged as a Claude Code plugin, so you can drive it from inside a session instead of dropping to a shell. The bough binary itself still comes from Install above (install it on PATH first; the plugin's commands shell out to it).

/plugin marketplace add threecorp/bough
/plugin install bough-all@bough

Three variants share one tree — pick by what you want acting on your sessions:

Plugin Ships Install when
bough commands + skill You want /bough:* on hand. Inert until invoked, so it is safe at any scope.
bough-hooks hooks You drive bough from the shell and only want the observe/inject loop.
bough-all commands + skill + hooks You want the lot in one line.

Installing wires the user-facing surface:

  • Slash commands — type them in any session: /bough:create <name>, /bough:remove <name>, /bough:list, /bough:status, /bough:verify <name>, /bough:doctor, /bough:instinct-status, /bough:instinct-list, /bough:instinct-promote, /bough:evolve, /bough:config-validate. Each one shells out to bough and summarises the result.
  • Skillusing-bough, model-invoked guidance on which /bough:* fits an intent, with a command -v bough PATH preflight.

Commands and the skill are inert until invoked — they observe and inject nothing until you type one — so the bough variant is side-effect-free at any scope.

Hooks are the part to scope deliberately. They carry the observe → instinct → inject → evolve → preserve loop (plus WorktreeCreate / WorktreeRemove) and fire on every event in whatever scope they are installed at, so install a hook-bearing variant into the repo you actually want observed:

claude plugin install bough-all@bough --scope project   # this repo only  (recommended)
claude plugin install bough-all@bough --scope user      # every repo on the machine

Project scope writes enabledPlugins into that repo's .claude/settings.json and the hooks fire only in sessions started there. The claude plugin install CLI defaults to user scope when --scope is omitted, so pass it.

Prefer no plugin? The CLI installs the same artifacts from the binary's embedded copy — bough claude hook|skill|command install --scope project. Wire hooks one way or the other, not both: they run the same dispatcher, so keeping both fires every event twice (bough claude doctor flags it).

LLM instinct minting stays opt-in on top of either route (bough instinct observer start, or .bough.yaml instinct.observer.autostart).

See docs/PLUGIN_CLAUDE_CODE.md for the variant layout, the full command / hook reference, and the CLI equivalents.

Quick start

Drop a .bough.yaml at the monorepo root that declares which sub-repos hang off worktrees/<name>/ and which engines start per worktree (v0.3.x .worktree-isolation.yaml is auto-read with a deprecation warning — see docs/MIGRATION-v0.3-to-v0.4.md):

schema_version: 2

monorepo_root: "."

repositories:
  - name: demo-api
    branch_strategy: develop
    direnv: true
    env_local:
      DEMO_API_DSN: "root:@tcp(127.0.0.1:{{ .Mysql.Port }})/demo?parseTime=true"
      DEMO_API_URI: "grpc://0.0.0.0:{{ index .Ports `api` }}"

  - name: demo-dbmigration
    branch_strategy: develop
    direnv: true
    role: engine-provider
    env_local:
      DEMO_DBM_PORT: "{{ .Mysql.Port }}"
    post_create:
      # Use whichever shell / toolchain your repo standardises on —
      # bough only runs the command, it does not assume Nix here.
      - "make migrate"

engines:
  - kind: mysql           # plugin discovery key (matches bough-plugin-mysql)
    version: "8.4"
    port_ranges:
      main: [42000, 44999]
    socket_dir: "/tmp"
    initial_resources:
      - { type: database, name: demo }
    # backend: nix        # optional; auto-detects nix-with-flakes / docker when omitted
    # ready_timeout_sec: 600  # v0.1.1+; default 600s for nix cold paths

  # Multi-port engine example — plugin lands in v0.5+; schema is ready in v0.4.
  # - kind: rabbitmq
  #   version: "3-management"
  #   port_ranges:
  #     amqp:       [60000, 60499]
  #     management: [60500, 60999]
  #   initial_resources:
  #     - { type: vhost, name: dev }

  # Wrap an EXISTING docker-compose.yml instead of provisioning a new
  # engine — see "Compose-wrapped services" below.
  # - kind: compose
  #   version: "7-alpine"      # descriptive only; the real version lives in the compose file
  #   port_ranges:
  #     main: [59000, 59999]
  #   compose:
  #     file: "demo-api/compose.yml"   # relative to the monorepo worktree root
  #     service: "redis"
  #     target_port: 6379

  # Elasticsearch with engine-managed plugins (docker backend). bough
  # generates the official elasticsearch-plugins.yml and lets ES install
  # them idempotently on boot — no custom entrypoint. Auxiliary plugin
  # files a plugin needs at runtime (e.g. an analyzer dictionary) mount
  # from a host dir via extras.es.config_mount.
  # - kind: elasticsearch
  #   version: "7"
  #   port_ranges:
  #     main: [56000, 58999]
  #   extras:
  #     es.mem_limit: "2g"          # docker --memory cap (default: 2x heap) — guards the host VM from OOM
  #     es.config_mount: "demo-api/es-config/analyzer"   # host dir, relative to the monorepo worktree root
  #   plugins:
  #     - id: analysis-icu          # official plugin: id only
  #     - id: analysis-example      # third-party plugin: id + a direct download URL
  #       location: "https://example.com/analysis-example-7.17.0.zip"

ports:
  api:    { range: [45000, 47999] }

registry:
  path: ".bough/ports.json"   # pre-v0.11 monorepos may keep ".bough-ports.json"
  backup_dir: "~/.bough/backups"

teardown:
  remove_branch: true
  remove_datadir: true
  graceful_timeout_sec: 10

Then wire it into Claude Code's WorktreeCreate / WorktreeRemove hooks in .claude/settings.json. bough claude hook install writes these (and the continuous-learning hooks) for you, all routed through the single bough hook handle dispatcher:

{
  "hooks": {
    "WorktreeCreate": [
      {"hooks": [{"type": "command", "command": "bough hook handle --event WorktreeCreate"}]}
    ],
    "WorktreeRemove": [
      {"hooks": [{"type": "command", "command": "bough hook handle --event WorktreeRemove"}]}
    ]
  }
}

Wire each event through one command. bough hook handle --event WorktreeCreate and the older bough create --stdin-json both run the full create pipeline, so keeping both for the same event runs it twice (a second post_create migration pass). bough claude hook install only ever manages its own bough hook handle entries, so prefer it and don't also hand-add a bough create --stdin-json group.

After that, claude --worktree F-FeatureName deterministically:

  1. Allocates a port set (one per declared engine role + per ports: kind) for the branch
  2. Materialises every declared sub-repo via git worktree add
  3. Spawns each configured engine via the matching bough-plugin-<kind> gRPC plugin
  4. Polls for readiness and renders each .env.local template
  5. Runs any per-repo post_create hooks (migrations, seed-force, etc.)

bough remove (or the WorktreeRemove hook) reverses all of the above: graceful plugin Down → lsof PID kill fallback → git worktree remove per sub-repo → registry cleanup → datadir teardown.

Workspace layout & resumable worktree sessions

Everything bough generates at the monorepo root is grouped under two directories (v0.11+):

<monorepo-root>/
  .bough/repos/<name>     # source checkouts (bough clones `source:` here)
  .bough/ports.json       # port registry
  worktrees/<name>/       # per-feature worktrees

So a git-initialised monorepo root needs just two .gitignore entries:

.bough/
worktrees/

Why git-init the root? Claude Code's --worktree is git-native. In a non-git root, bough's WorktreeCreate hook still lets claude --worktree run (the hook is Claude Code's documented escape hatch for non-git / other-VCS workspaces), but Claude Code anchors such hook-based worktree sessions to the launch directory. That means:

  • claude --resume <id> from the monorepo root — always works.
  • claude --worktree <name> --resume <id> — cannot find the session (it looks in the worktree's own project bucket, where a non-git hook-based session was never stored).

git init the monorepo root and Claude Code switches onto the git-native path, making claude --worktree <name> --resume <id> work too. bough create prints a one-time heads-up (with the two .gitignore lines above) when it notices the root is not a git repository — it never edits .gitignore for you.

Upgrading from a pre-v0.11 layout is transparent. A monorepo whose checkouts still sit at <root>/<name> and whose worktrees still live under .worktrees/ keeps working unchanged — bough detects and reuses the existing locations, and only fresh checkouts / worktrees adopt the new paths. To fully consolidate, move the sub-repo checkouts into .bough/repos/, rename .worktrees/worktrees/, and (optionally) .bough-ports.json.bough/ports.json.

Compose-wrapped services

The four bundled engines above are ones bough fully provisions itself (nix flake or a bough-managed Docker container). kind: compose is different: it wraps a docker-compose.yml you already have — no duplicate nix flake, no second source of truth for the image/version — and gives it only the one thing bough is actually good at: deterministic, worktree-scoped port isolation.

engines:
  - kind: compose
    version: "7-alpine"       # descriptive only; the compose file owns the real version
    port_ranges:
      main: [59000, 59999]    # HOST port range bough allocates from
    compose:
      file: "demo-api/compose.yml"  # relative to the monorepo worktree root
      service: "redis"              # only this service is touched — siblings in
                                     # the same file are left alone
      target_port: 6379             # the CONTAINER-side port the service listens on
      # project: ""                 # optional; default "bough-<worktree>-<file>"
      # env_prefix: ""              # optional; default upper(service) → BOUGH_REDIS_*

What bough does under the hood, without ever editing your compose file: it renders a small worktree-scoped override (fixed host port + a bough-compose-<port> container name) and runs docker compose -f demo-api/compose.yml -f <override> -p <worktree-scoped-project> up -d redis. Two worktrees pointing at the textually-identical file never collide — different project, different container, different port.

Trade-offs versus the four native plugins, by design:

  • teardown.remove_datadir: true does not touch compose-managed volumes. Down stops and removes the container; deleting the data your compose file's own volumes hold is left to you, since bough does not own that lifecycle here.
  • ReadyCheck defaults to a plain TCP dial, not a protocol-level handshake (unlike the native plugins' mysql/redis/postgres/HTTP probes). Set extras: {compose.ready_probe: "redis"} (or postgres / mysql / http) on the engine entry if you want a real protocol check instead. Set this explicitly when wrapping mysql — a bare TCP dial goes ready during the mysql image's first-run "temporary server" bootstrap phase, the same race the bundled mysql plugin's own Docker backend had to fix; mysql reads the server's handshake packet instead of just dialing.
  • One compose service per Engine entry. Wrapping two services from the same compose file needs two separate kind: compose engine entries pointing at the same file with different service/target_port values — bough tears down each independently but never removes the file's shared network, so it is left behind as harmless cruft after the last one exits.

CLI surface

# Worktree isolation
bough create [--config PATH] [--name NAME] [--stdin-json] [--cwd PATH]
bough remove [--config PATH] [--name NAME | --path PATH] [--stdin-json]
bough verify <worktree-name>            # registry vs declared ranges vs .env.local
bough status [--json]                   # registry + lsof TCP listen probe
bough list                              # registry table (kinds dynamic)
bough backfill                          # register pre-existing worktrees/* (or legacy .worktrees/*)
bough config validate [PATH]            # strict YAML schema check
bough plugins list                      # glob $PATH for bough-plugin-*

# What bough installs into Claude Code
bough claude hook install | uninstall | list     # hook wiring in .claude/settings.json
bough claude skill install | uninstall | list    # the using-bough skill
bough claude command install | uninstall | list  # the /bough:* commands
bough claude doctor                              # hook wiring + observer capture + cost posture

# Continuous learning (you run these; the daemon needs instinct.observer.autostart)
bough instinct observer run-once | start        # mint instincts via claude --print
bough instinct list | show <id>                 # inspect the captured corpus
bough instinct evolve --generate                # cluster instincts → skills / agents / commands
bough instinct import                           # interop with an everything-claude-code corpus

The hook dispatcher's own verbs (inject-context, session-end, preserve-instincts, session-evolve-claudemd) are fired by bough hook handle, not typed. They stay reachable for debugging but are out of --help. bough hook / bough doctor still work as deprecated aliases of their bough claude ... homes for the v0.x line.

Repository layout

bough/
├── cmd/
│   ├── bough/                              host CLI entrypoint
│   ├── bough-plugin-mysql/                 MySQL plugin entrypoint
│   ├── bough-plugin-postgres/              PostgreSQL plugin entrypoint
│   ├── bough-plugin-redis/                 Redis plugin entrypoint
│   ├── bough-plugin-elasticsearch/         Elasticsearch plugin entrypoint
│   └── bough-plugin-compose/               Compose-wrapper plugin entrypoint
├── internal/                              # worktree isolation core
│   ├── cli/                                cobra subcommands
│   ├── config/                             .bough.yaml schema (validator/v10)
│   ├── allocator/                          crc32 + linear-probing port allocator
│   ├── registry/                           .bough/ports.json atomic R/W (legacy .bough-ports.json read fallback)
│   ├── gitwt/                              `git worktree` wrapper
│   ├── envwriter/                          text/template + Sprig .env.local generator
│   ├── hooks/                              post_create / pre_remove hook runner
│   ├── backend/                            nix / docker backend auto-detect
│   ├── pluginhost/                         go-plugin discovery + lifecycle
│   ├── pluginsign/                         plugin binary signature verification
│   │                                       # continuous learning (v0.9)
│   ├── homunculus/                         instinct corpus (~/.local/share/bough-homunculus/)
│   ├── observe/                            observations.jsonl writer + Anthropic-env scrub
│   ├── prompts/                            //go:embed prompt templates + 3-layer override
│   ├── provider/claudecli/                 `claude --print` subprocess + rate limiter
│   ├── evolve/                             instinct → skill / agent / command 5-gate pipeline
│   ├── qualitygate/                        operator-supplied lint / typecheck gates
│   ├── inject/                             UserPromptSubmit context-block builder
│   └── session/                            SessionEnd summary + confidence update
├── plugins/
│   └── engine/
│       ├── api/                            gRPC EngineProvider contract + Go interface
│       ├── mysql/                          MySQL 8.4 provider + embedded services-flake
│       ├── postgres/                       PostgreSQL 16 provider + embedded services-flake
│       ├── redis/                          Redis 7 provider + embedded services-flake
│       ├── elasticsearch/                  Elasticsearch 7 provider + process-compose-flake
│       └── compose/                        Wraps an existing docker-compose.yml/service
├── tests/
│   └── integration/                        real-services E2E (build tag: integration)
├── flake.nix                               devShells.ci / devShells.default
├── .goreleaser.yaml                        cross-compile + GitHub Release
└── .github/workflows/                      ci.yml + release.yml

Continuous learning (v0.9)

bough is not an agent memory system. bough is a per-worktree memory-orchestration layer. The continuous-learning loop is a verbatim Go port of the everything-claude-code reference architecture, so every LLM call stays inside your Claude Code subscription (see Cost & billing).

The loop is observe → evolve → inject, entirely opt-in and off by default:

  1. Observe. Claude Code hooks (SessionEnd, PreCompact, UserPromptSubmit, …) append raw session events to observations.jsonl and mint instincts — confidence-scored behavioural rules — into an on-disk corpus (the "homunculus") under ~/.local/share/bough-homunculus/<project-id>/ (project-id = sha256[:12] of the credential-stripped git remote, else the repo path). Env scrubbing strips every ANTHROPIC_* / Bedrock / Vertex key so a spawned claude --print can never flip to API billing.
  2. Evolve. bough instinct evolve --generate clusters related instincts through a 5-gate pipeline (the final gate is an LLM judge via claude --print --output-format json) and emits Claude Code artifacts — SKILL.md, agents, and commands — into the repo's .claude/ (project-scope since v0.9.20; bough create symlinks each worktree's .claude/skills at the monorepo copy). Generated artifacts cite a resolvable source-instinct path so a reader can trace a skill back to the instincts it came from (v0.9.22).
  3. Inject. The UserPromptSubmit hook prints a confidence-ranked instinct block into your next turn as ordinary input tokens — no separate call.
# Wire bough's hook handlers into .claude/settings.json (idempotent;
# hand-edited rows are preserved) and inspect the posture.
bough claude hook install
bough claude doctor              # hook wiring + observer capture + cost meter

# Mint instincts from recent observations, then review the corpus.
bough instinct observer run-once          # one claude --print pass
bough instinct list              # confidence-ranked corpus
bough instinct show <id>

# Cluster the corpus into skills / agents / commands.
bough instinct evolve --generate          # 5-gate pipeline; writes <repo>/.claude/*

# Interop with an existing everything-claude-code corpus.
bough instinct import

Enable it per-monorepo in .bough.yaml (off by default):

instinct:
  enabled: true
  observer:
    autostart: true      # opt-in: auto-run the minting daemon per session
    interval_sec: 600     # minting cadence; optional, defaults to 10 min

observer.autostart is the "opt-in once, then automatic" switch: with it on, the UserPromptSubmit hook ensures the bough instinct observer start daemon is running for this monorepo, so instincts are minted automatically without a manual start per machine. It is off by default — the daemon calls claude --print, so bough never starts it silently, and bough claude doctor always reports whether it is running. Minting stays subject to the self-DoS limiter. (This auto-mints instincts only; turning them into skills/agents/commands is still the explicit bough instinct evolve --generate.)

LLM calls happen only in the explicit bough instinct observer run-once / bough instinct evolve --generate (and the opt-in bough instinct observer start daemon) — each a claude --print subprocess under your subscription, hard rate-limited (10 / session, 30 / hour, 3-failure circuit breaker). Everything else — hooks, ingest, clustering gates 1-4 — is pure local filesystem.

See docs/EVOLVE.md for the 5-gate evolve pipeline.

v0.5-v0.8 superseded. Earlier releases explored a different continuous-learning design. v0.9.0 reset to the ECC verbatim port above; pin v0.8.1 if you depend on the earlier surface. See the v0.9.0 CHANGELOG entry.

Roadmap

Milestone Headline
v0.1.0-α Nix services-flake backend, 4 DB plugins (mysql / postgres / redis / elasticsearch)
v0.1.1 Bundled flake.lock per plugin (cold start 5-10 min → 30-60 s), packages.default for nix run / nix profile install, per-engine ready_timeout_sec config, honest README
v0.2.0 Docker backend, hybrid backend: selector — explicit nix / docker in YAML, or auto-detect (Nix-with-flakes present → Nix, else Docker daemon → Docker, else clear error) when the field is omitted
v0.3.0 Plugin conformance suite + CI matrix on real Docker — plugin authors verify their contract end-to-end with one test func, four bough-internal plugins are gated on ubuntu-24.04 + ubuntu-24.04-arm × mysql / postgres / redis / elasticsearch
v0.4.0 Generic engine plugin orchestrator (was: DB-only). DBProviderEngineProvider, plugins/db/plugins/engine/, YAML schema v2 (.bough.yaml / engines: / port_ranges: per role / initial_resources:). Multi-port engines (rabbitmq AMQP+Management, kafka broker+controller, NATS client+monitor+cluster) are first-class; v0.4.x reads every v0.3 surface with a deprecation warning — only the plugin gRPC handshake (DBProvider/BOUGH_DB_PLUGIN) was removed in v0.5.0, the YAML-level fallback (old file name / section / field names) is still read today, see docs/MIGRATION-v0.3-to-v0.4.md
v0.5.0-v0.8.0 (superseded) An earlier continuous-learning design, replaced wholesale in v0.9.0; pin v0.8.1 if you depend on it
v0.9.0 The "ECC verbatim port" reset. Deleted the v0.5-v0.8 surface and rebuilt continuous learning as a faithful Go port of everything-claude-code: the ~/.local/share/bough-homunculus/ corpus, observations.jsonl, and a subscription-only claude --print mechanism (no Anthropic API, no separate billing)
v0.9.1-v0.9.22 The observe → evolve → inject loop: bough instinct evolve --generate 5-gate clustering into skills / agents / commands, UserPromptSubmit instinct injection, SessionEnd / PreCompact hooks, secret-scrub at capture, project-scope evolved skills (v0.9.20), resolvable source-instinct paths (v0.9.22), plus a retrospective /review bug-fix sweep of the merged infra PRs
next Reference rabbitmq / kafka / NATS / minio engine plugins, Homebrew tap

Status

v0.9.22 (current; v0.9.23 in progress). Three of the four bundled engine plugins (bough-plugin-{mysql,redis,elasticsearch}) are battle-tested in a real Go + Rails + Remix multi-sub-repo monorepo (MySQL 8.4 LTS + Redis 7 + Elasticsearch 7) on the Docker backend; the Nix backend remains supported via auto-detect and is the default when nix-with-flakes is on PATH. The Postgres plugin (bough-plugin-postgres) is integration-test-only — it has not run in that production monorepo. Multi-port engines (rabbitmq / kafka / NATS) are first-class in the contract — reference plugins are not yet bundled.

The worktree-isolation core has been stable since v0.4.0. v0.5 onward layers on the opt-in continuous-learning loop. v0.9.0 reset that loop to a verbatim Go port of the everything-claude-code reference architecture (subscription-only, no API billing) and superseded the earlier v0.5-v0.8 surface wholesale — pin v0.8.1 if you depend on it.

Plugin conformance

Every PR's CI runs the bough/conformance suite against a real Docker container, one (runner × plugin) cell at a time. Plugin authors (internal or third-party) verify their contract with one test function:

//go:build conformance
func TestMyPluginConformance(t *testing.T) {
    conformance.Run(t, conformance.Config{
        PluginBinary: os.Getenv("BOUGH_CONFORMANCE_PLUGIN_BIN"),
        Image:        "myengine:1.0",
    })
}

Locally:

make build
make conformance-local PLUGIN=mysql       # one plugin
make conformance-all                       # all four

See docs/PLUGIN_AUTHOR_GUIDE.md for the walkthrough, plugins/engine/api/CONTRACT.md for the prose contract every assertion traces back to, and examples/plugin-template/ for a copy-this skeleton with TODO markers.

Contributing

Bug reports and pull requests welcome — please run make test, make lint, and make build locally before opening a PR. For plugin work also run make conformance-local PLUGIN=<kind> (needs Docker).

License

MIT. See LICENSE for the full text.

About

Per-worktree isolation AI-Orchestrator for monorepos.

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages