# Nested bridge layout Some agents are sandboxed to the project directory they are working in. They cannot reach a Ferryman checkout that lives in a *sibling* folder (for example `C:\orchestrator-bridge` next to `C:\myproject`), so they cannot find or talk to the bridge. The fix is to put a full, self-contained bridge **inside** the project — without polluting the project's git history. ## The model ``` myproject/ <- the main project repo (its own git) ├─ .gitignore <- contains "/.ferryman/" ├─ src/ ... └─ .ferryman/ <- a FULL Ferryman bridge, and its OWN git repo ├─ .git/ <- independent history; push it to its own remote if you like ├─ .gitignore <- ignores .data/ (runtime state) ├─ bridge.toml <- endpoint + project slug ├─ start.ps1 / start.sh ├─ README.md └─ .data/ <- SQLite db, artifacts, workspaces, memory, recovery ``` Two independent git repositories share one folder tree: - The **parent** project gitignores `/.ferryman/`, so the bridge never shows up in the project's `git status`, diffs, or commits. - The **bridge** is its own repo (`.ferryman/.git`). Git treats a nested `.git` as a boundary and never descends into it, so the two histories stay fully separate. You can give the bridge its own remote and version it as a sub-project, exactly like any other repo. Runtime state (`.data/`) is gitignored inside the bridge repo too — only the config, helper scripts, and README are tracked there. ## Set it up From the Ferryman repo, run the helper against your project: ```powershell # Windows powershell -File scripts\nest-bridge.ps1 -Project C:\code\myproject -Port 8795 ``` ```sh # Linux / macOS scripts/nest-bridge.sh /code/myproject 8795 demo ``` Arguments: project directory (default: current dir), port (default: 8787), project slug (default: `demo`). The script is idempotent — safe to re-run. It will: 1. add `/.ferryman/` to the project's `.gitignore` (if the project is a git repo), 2. create `.ferryman/` and `git init` it as its own repo, 3. write Ferryman's own `.gitignore`, `bridge.toml`, a `start` helper, and a README. ## Run it The bridge needs the `ferryman-server` binary. Put it on `PATH`, or point the start helper at it with `FERRYMAN_BIN`: ```powershell $env:FERRYMAN_BIN = "C:\orchestrator-bridge\target\release\ferryman-server.exe" cd C:\code\myproject\.ferryman powershell -File start.ps1 ``` ```sh export FERRYMAN_BIN=/path/to/ferryman-server cd /code/myproject/.ferryman && ./start.sh ``` No recovery key is required for local/dev use — the server mints an ephemeral one and warns (see the README quickstart). The API comes up on `http://127.0.0.1:` and every file it writes stays under `.ferryman/.data/`. ## One bridge per project In this full-bridge layout each project runs its **own** server, so give each project a **distinct port** (8795, 8796, …). Agents working in a project point at that project's `http://127.0.0.1:` and use its scoped project token — the same Ferryman HTTP API as always, just reachable from inside the sandbox. ## Running the bridge in WSL / Ubuntu (Linux-side data) On Windows, running the bridge as a Linux process (WSL/Ubuntu) is often more robust: real daemonization via systemd, and agent CLIs launch as plain executables (no `.cmd`/`.ps1` shim handling). **One caveat matters:** SQLite on a Windows drive mounted into WSL (`/mnt/*`, DrvFs) has file-locking bugs that cause intermittent "database is locked/busy" failures. Keep the database on the Linux filesystem. `scripts/wsl-bridge.sh` does exactly that: it keeps a discoverable, gitignored `.ferryman/bridge.toml` inside the Windows project (so sandboxed agents find the endpoint), but puts the SQLite `.data/` on Linux (`~/ferryman//.data`) and runs the server as a durable systemd service. ```sh # inside WSL; build ferryman-server first: cargo build --release -p ferryman-server export FERRYMAN_BIN=$HOME/ferryman/ferryman-server export FERRYMAN_SUDO_PW=... # omit if sudo is passwordless scripts/wsl-bridge.sh /mnt/x/myproject 8796 myproject ``` The API comes up on `http://127.0.0.1:8796`, reachable from **both** WSL and Windows-native processes via WSL2 localhost forwarding. Manage it with `systemctl status|restart ferryman-`. Give each project its own port. Rule of thumb: **the SQLite database lives on the same OS that runs the server.** A Windows-native server keeps data on the Windows drive; a WSL server keeps data on the Linux filesystem. ## Many repos, one instance (shared hub + attach) When a machine hosts a lot of repos, do not run a bridge per project — run **one** shared hub and attach each repo to it. Each repo still keeps a full, own-git `.ferryman/` directory (gitignored by the parent); it just holds the repo''s config + scoped token instead of a server. The `ferryman-server` software runs once; the directories are per-repo. ```sh # once per machine: bring up the single hub (durable systemd, Linux-side data) export FERRYMAN_BIN=$HOME/ferryman/ferryman-server export FERRYMAN_SUDO_PW=... # omit if passwordless sudo scripts/hub-up.sh 8796 # per repo: attach it as a scoped project in the one hub scripts/attach-bridge.sh /mnt/x/myproject myproject ``` `attach-bridge.sh` ensures the one hub is up (idempotent — it never starts a second instance), creates the repo''s own-git `.ferryman/`, registers the repo as a project, and stores a scoped token in `.ferryman/token` (gitignored, local-only, never committed). Agents read `.ferryman/bridge.toml`, connect to the hub with that token, and are isolated to their project by the API. Trade-off: one instance means one process + one database for all projects (isolation is by token + workspace, not by separate processes). If a specific repo needs hard process/DB isolation, give it a dedicated bridge on its own port (`wsl-bridge.sh` / `nest-bridge.ps1`) instead — a hybrid is fine. ## Updates — approve/deny gate A nested bridge does not track `main` on its own, and nothing updates automatically. The flow is an explicit gate (`ferryman-updater`): - **Check (read-only):** `ferryman-updater check-remote --checkout --branch main` fetches the canonical origin and lists the commits you would be pulling. It changes nothing and exits 10 when an update is pending — so an agent can surface "update available" to you without ever applying it. - **Approve:** `ferryman-updater update-bridge --checkout --branch main --confirm` fast-forwards the install (clean checkout + already-configured origin only). Without `--confirm` it prints the pending diff and refuses, so it can never apply blind. - **Deny:** do nothing. The bridge stays pinned where it is. Per-project opt-in is unchanged: a project only accepts a recorded release when its `bridge-project.toml` has `opt_in = true` (`ferryman-updater check` / `apply`). Agents should run **check-remote** and report; a human runs **update-bridge --confirm** to approve. Never wire auto-apply. **Auto-updateable, with permission.** `scripts/update-check.sh` runs on a daily `systemd` timer (`ferryman-update-check.timer`): it is read-only, and when the canonical repo is ahead it records `~/ferryman/UPDATE_AVAILABLE` with the pending commits — it never applies. Approving is one explicit command, `scripts/apply-update.sh --confirm`, which fast-forwards the source, rebuilds, swaps the binaries, and restarts the hub. Without `--confirm` it just shows what would change. So the machine notices updates on its own but only a human ever applies one. ## Migrating from a sibling bridge If a project was using a bridge in a sibling folder, you do not lose anything: - Stand up the nested bridge with the script above (new `.data/`). - Point the project's workers/agents at the nested endpoint + token instead of the sibling one. - The old sibling checkout can keep running in parallel until you have proven the nested one; nothing is deleted or moved for you. The bridge's git repo (`.ferryman/.git`) can be pushed to its own remote so the sub-project's config/history is backed up independently of the main project.