banyan

Concepts

The mental model — project, feature, worktree, agent, orchestrator, stack.

Banyan's mental model is small but specific. Five concepts cover everything.

Project

A group of repos that ship together. Examples:

  • frontend + backend + mobile app + infra stack
  • a Lerna-style multi-package codebase (one banyan "repo" per package)

Projects are declared in ~/.config/banyan/config.yaml. Each project has a name and a list of repos.

Repo

A single git repository (or a type: compose docker-compose definition that's not actually git). Each repo has:

  • a path (where its main checkout lives)
  • a base branch (what to rebase / merge against)
  • a run config (command + port + env)
  • optional: copyOnWorktree, loadEnvFiles, hooks

Feature

A unit of work that gets:

  • One git branch (feature/<name>) checked out as a worktree in every repo
  • One Claude agent pane with --add-dir on every worktree
  • One docker compose stack per compose-type repo, on dynamically-allocated host ports
  • One tmux test window (one pane per repo running its run command)

Features live in parallel. Banyan ensures none of them step on each other.

Worktree

A git worktree-managed checkout, separate from the main checkout. Layout:

~/Documents/Dev/myapp/
  front/                       ← main checkout
  worktree-front/
    login/                     ← worktree for feature "login"
    payment/                   ← worktree for feature "payment"
  back/
  worktree-back/
    login/
    payment/

Each worktree is fully independent: you can have front/worktree-front/login running on port 3001, front/worktree-front/payment on 3002, both pointing at their own backend on 8081 / 8082.

Agent

A Claude Code instance running in a tmux pane with --add-dir on every repo's worktree for a feature. Has the banyan MCP server wired in. Conversations resume across reboots via claude --continue.

Two agent modes:

  • live — conversational, no ceremony. The agent is banyan-aware (knows about the MCP tools) but you drive. Can edit the main branch directly. Default when you don't pass a prompt.
  • delegated — pipeline-gated: Setup → Plan → human review → Execute → Report → Merge. The agent loops via a Stop hook until banyan_report_done is signaled. Default when you spawn with bn wt -p "<prompt>".

The mode is per-feature and switches with -m live | -m delegated on bn wt. Legacy mode names (interactive, assisted, autonomous, autopilot) are still accepted and normalized to the closest equivalent.

Orchestrator

A second Claude agent that lives above the features. Has:

  • --add-dir on every repo
  • The banyan MCP server (can search transcripts across features, dispatch tasks via the dashboard, predict cross-feature conflicts, drive merges)

The orchestrator is launched automatically by bn <project> start. It sees everything; per-feature agents only see their feature. Restart it any time with bn <project> restart-orchestrator.

Dynamic port allocation

Each repo declares a canonical port in its config — the port the app would normally bind:

repos:
  - name: front
    run:
      port: 3000
      portEnv: PORT
  - name: back
    run:
      port: 8080
      portEnv: SERVER_PORT

When you bn start <feature>, banyan probes from canonical + 1 upward (3001, 3002, …) and grabs the first free one. "Free" means it can bind on both 0.0.0.0 and 127.0.0.1 — testing only loopback misses ports already bound on the wildcard address. The allocated port is injected via the portEnv you declared, so the run command picks it up like any normal env var:

back  : SERVER_PORT=8081 ./gradlew bootRun
front : PORT=3001 npm run dev

Cross-repo wiring

The front-end needs to know which back-end port to hit — and since both are allocated at spawn time, you can't hard-code them. Banyan resolves cross-repo references via {{<repo>.port}} templating:

- name: front
  run:
    env:
      REACT_APP_API_URL: http://localhost:{{back.port}}

At spawn time, {{back.port}} becomes the allocated port of the back repo for this same feature — so the login feature's front talks to the login feature's back, never to tag-filter's.

Compose service ports

For repos with a docker-compose stack, banyan reads the host port docker assigned to a service and injects it via composePorts:

- name: back
  run:
    composePorts:
      DB_PORT: mysql-dev:3306

DB_PORT becomes whatever host port docker mapped to the compose service mysql-dev's container port 3306 — which, like the canonical ports, is per-feature isolated.

Why this matters

Allocate dynamically, inject as env, template cross-repo: that's how banyan runs 10 features on the same machine without port collisions or .env editing. bn ports [feature] prints the current allocations, and the state is persisted to ~/.config/banyan/state/<project>.<feature>.json so it survives shell reloads.

On this page