From 987d194b828736c3ab804f5685433044327d696b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Oct 2026 07:27:47 +0000 Subject: [PATCH 1/5] Initial plan From d5a9453f119e1e84a81bfe04273d5c69ed8f0639 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Oct 2026 07:34:33 +0000 Subject: [PATCH 2/5] feat(herdr): add opt-in host control API connectivity Co-authored-by: jsburckhardt <18494471+jsburckhardt@users.noreply.github.com> --- README.md | 7 ++ src/herdr/README.md | 176 ++++++++++++++++++++++++++++ src/herdr/devcontainer-feature.json | 12 +- src/herdr/herdr-client.sh | 35 ++++++ src/herdr/install.sh | 22 ++++ test/herdr/outside-of-container.sh | 45 +++++++ test/herdr/scenarios.json | 12 ++ test/herdr/test.sh | 1 + 8 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 src/herdr/herdr-client.sh create mode 100644 test/herdr/outside-of-container.sh create mode 100644 test/herdr/scenarios.json diff --git a/README.md b/README.md index cb9efb6..ae83df6 100644 --- a/README.md +++ b/README.md @@ -775,6 +775,13 @@ Running `herdr --version` inside the built container will print the version of h herdr --version ``` +To share a host Herdr server across Dev Containers, enable `outsideOfContainer` +and explicitly bind mount its socket directory at runtime. See the +[Herdr Feature guide](src/herdr/README.md#share-one-host-herdr-server-linux) for +the required configuration, UID permissions, multi-container agent setup, +platform limitations, and security risks. Enabling the option alone does not +mount the host socket or register agents. + ### `colibri` Running `colibri` inside the built container will print the usage banner of colibri. The installed version is recorded in `/usr/local/share/colibri/VERSION`. diff --git a/src/herdr/README.md b/src/herdr/README.md index b10877c..2943533 100644 --- a/src/herdr/README.md +++ b/src/herdr/README.md @@ -16,8 +16,184 @@ herdr is a fast, cross-platform CLI tool from herdrdev/herdr distributed as a st | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| | version | Version of herdr to install (e.g. 'latest' or a specific tag like 'v0.7.3'). | string | latest | +| outsideOfContainer | Use only an external Herdr control API; requires an explicit runtime bind mount. | boolean | false | +| containerSocketPath | Absolute API socket path inside the container in external-client mode. | string | /run/herdr-host/herdr.sock | +## Share one host Herdr server (Linux) +Outside mode installs the same release binary but makes `herdr` a client-only +wrapper. Native API commands use `HERDR_SOCKET_PATH` (or `containerSocketPath` +when unset). Bare `herdr`, server/TUI launch, and session/remote routing are +rejected: no local server is automatically started. Help and version work +without a socket. Default mode remains unchanged. + +Use compatible host/container versions. The example pins both to **v0.9.4**, +which supports `agent prompt`; older releases such as v0.7.3 do not. + +On the Linux Docker host, install Herdr, then start **one** server: + +```bash +install -d -m 700 "$HOME/.local/share/herdr-control" +HERDR_SOCKET_PATH="$HOME/.local/share/herdr-control/herdr.sock" herdr +``` + +Start each of Repo A, Repo B, and Repo C with this `.devcontainer/devcontainer.json`: + +```json +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "remoteUser": "vscode", + "updateRemoteUserUID": true, + "features": { + "ghcr.io/jsburckhardt/devcontainer-features/herdr:1": { + "version": "v0.9.4", + "outsideOfContainer": true, + "containerSocketPath": "/run/herdr-host/herdr.sock" + } + }, + "mounts": [ + "source=${localEnv:HOME}/.local/share/herdr-control,target=/run/herdr-host,type=bind,readonly" + ] +} +``` + +**The mount is required in addition to the option.** Feature installation runs +during image build; Feature mount metadata is unconditional and cannot +interpolate Feature options. To keep host access explicitly opt-in, this Feature +does not declare an automatic mount or a misleading `hostSocketPath` option. +Configure the host source in `mounts` and the container destination in +`containerSocketPath`. No privileged mode or Docker socket is needed. +`readonly` prevents filesystem modification, **not** API writes. + +Mount the **directory**, not the individual socket: Herdr replaces sockets when +restarted, and a file bind mount can retain a stale inode. Create the host +directory before creating/rebuilding containers; keep it in place across server +restarts. This configuration survives container rebuilds. + +For an already-running default server, its API socket is +`~/.config/herdr/herdr.sock` (or `$XDG_CONFIG_HOME/herdr/herdr.sock`). +A named session uses `~/.config/herdr/sessions//herdr.sock`. +Mount the appropriate directory instead, but note that the default config +directory exposes other files. Prefer the dedicated socket directory above. +Do not pass `--session`: it overrides `HERDR_SOCKET_PATH`; choose a session by +mounting its socket. Do not mount only `herdr-client.sock`: that is the separate +binary TUI protocol, not the newline-delimited JSON control API. + +### Permissions and troubleshooting + +Herdr creates sockets with **0600** permissions. Use a non-root container user +with the same numeric UID as the host socket owner (`id -u`, `ls -ln`). +`updateRemoteUserUID` usually provides this on native Linux with the example +image. A matching GID alone does not grant access. Rootless Docker/user-namespace +mapping may change the effective host UID; check the mapped identity. +Alternatively, the host owner can deliberately grant a narrow user ACL on the +socket and directory, or a controlled group with mode 0660. Reapply socket ACLs +after every server restart. This Feature never changes host ownership or modes +and never grants world access. + +* **No Unix socket:** start Herdr on the host, check `mounts` and + `containerSocketPath`, and rebuild after changing mounts. A directory at the + socket path usually means the source did not exist when mounting. +* **Permission denied:** verify numeric UID, directory traversal permissions, + user-namespace mappings, and any host SELinux policy. Do not solve this with + `chmod 666` or privileged containers. +* **Server not running/connection refused:** the socket may be stale; restart the + host server and use a directory mount. Native CLI connection failures are + returned unchanged; the wrapper does not fall back to a container server. +* **Unknown command/protocol error:** align client and server versions and check + `herdr agent --help`. `herdr api snapshot` is a useful connectivity probe. + +### Host-owned agents and cross-repository communication + +The socket gives access to existing host-managed terminals; it does **not** +register arbitrary processes in a container. Create three workspaces/panes in +the host Herdr UI, then launch Copilot from each corresponding **host Herdr pane**. +Forward that pane's identity through `devcontainer exec`: + +```bash +devcontainer exec --workspace-folder "$HOME/repos/repo-a" \ + env HERDR_ENV=1 HERDR_PANE_ID="$HERDR_PANE_ID" \ + HERDR_WORKSPACE_ID="$HERDR_WORKSPACE_ID" HERDR_TAB_ID="$HERDR_TAB_ID" \ + HERDR_BIN_PATH=/usr/local/bin/herdr \ + HERDR_SOCKET_PATH=/run/herdr-host/herdr.sock \ + copilot +``` + +Repeat with repo-b and repo-c from their own host panes. Environment variables +are not automatically forwarded by `devcontainer exec`. `HERDR_BIN_PATH` must +refer to the **container** binary, not the host binary. Do not invent pane IDs or +copy one pane's ID into multiple agents. + +Host process detection may see only `devcontainer exec`, not the container's +Copilot process. **`HERDR_AGENT=copilot` is not a supported registration +mechanism.** Use Herdr's native reporting/integration support for lifecycle +updates; for example, from the agent's container environment: + +```bash +herdr pane report-agent "$HERDR_PANE_ID" \ + --source devcontainer-copilot --agent copilot --state idle --seq 1 +herdr agent rename "$HERDR_PANE_ID" backend +``` + +Give the other panes unique names such as `frontend` and `tests`. The integration +must report real transitions (`working`, `blocked`, `idle`, `unknown`) with +increasing sequence numbers; a one-off report is not automatic status tracking. +After exit, release the report using `herdr pane release-agent "$HERDR_PANE_ID" +--source devcontainer-copilot --agent copilot --seq `. +Consult [agent integration](https://herdr.dev/docs/add-herdr-support/) +and [agent automation](https://herdr.dev/docs/agent-automation/) for the installed +version. This Feature does not install a Copilot lifecycle integration. +The host still owns terminal input/output; prompt delivery, status, and +`--wait` depend on correct detection/reporting, not just socket connectivity. + +From any of the three containers: + +```bash +herdr agent list +herdr agent get backend +herdr agent prompt backend "What API contract are you implementing?" --wait --timeout 120000 +herdr agent read backend --source recent --lines 100 +``` + +Use the same host-issued IDs/names everywhere. Repeat in the opposite direction +with `frontend` and `tests` to validate A→B, B→A, A→C, and C→B. Responses are native +terminal output, not a separate mailbox. Agents must be running in their +host-owned panes and have a working status integration for reliable waits. + +### Platforms and alternative transport + +| Environment | Requirements / limitations | +|---|---| +| Linux, native Docker | Unix directory bind mount and matching UID/explicit ACL. Reference configuration above. | +| Windows, WSL2 | Run the Linux Herdr server and Docker integration in the same WSL distribution; keep the socket on its Linux filesystem, not `/mnt/c`. Native Windows Herdr uses named pipes, which cannot be mounted as a Linux Unix socket. | +| macOS, Docker Desktop | Ordinary file sharing across the VM boundary does not forward a macOS Unix socket listener. Direct mounting is not a supported configuration here. | +| GitHub Codespaces / remote Docker | A local workstation socket is not on the remote Docker host. Direct mounting cannot reach it. A server on the actual Linux Docker host can use the Linux setup. | + +Herdr has no native TCP/HTTP listener. Current Herdr supports authenticated SSH +machine commands (`herdr --machine