diff --git a/.claude/agents/command-builder.md b/.claude/agents/command-builder.md
index f477839..36e326a 100644
--- a/.claude/agents/command-builder.md
+++ b/.claude/agents/command-builder.md
@@ -117,7 +117,7 @@ Yes if it has side effects, is invoked deliberately by the user, and runs end-to
ls .claude/skills/
```
-Pick 2-3 task-shaped exemplars (e.g., this project's `release`, `ci-status`, `cloudflared-tunnel`) and read their SKILL.md to match house style — section structure, command-quoting conventions, how reports are formatted.
+Pick 2-3 task-shaped exemplars (e.g., this project's `release`, `ci-status`, `agent-browser`) and read their SKILL.md to match house style — section structure, command-quoting conventions, how reports are formatted.
### Phase 3 — Author SKILL.md
diff --git a/.claude/skills/agent-browser/SKILL.md b/.claude/skills/agent-browser/SKILL.md
index cc8459b..57cb72e 100644
--- a/.claude/skills/agent-browser/SKILL.md
+++ b/.claude/skills/agent-browser/SKILL.md
@@ -74,8 +74,8 @@ Rationale for the pins:
- **0.8.5** — later majors have breaking CLI changes; the rest of this
skill targets 0.8.5's flags.
-Or rebuild the image with `INSTALL_BROWSER=true` in the compose overlay
-to bake this in.
+Or rebuild the image with `INSTALL_AGENT_BROWSER=true` in
+`.devcontainer/.env` to bake this in.
After installing, resume the health check from step 2b — **do not stop**.
@@ -123,7 +123,7 @@ agent-browser close
### Step 2d — DNS resolution check (tunnel hostnames)
-If the URL contains a hostname that routes through a cloudflared tunnel (any `.` served via cloudflared), the container's DNS may not resolve it. Check and fix:
+If the URL contains a hostname served by a tunnel or reverse proxy, the container's DNS may not resolve it yet. Check and fix:
```bash
HOSTNAME=$(echo "$URL" | sed 's|https\?://||; s|/.*||; s|:.*||')
@@ -135,7 +135,7 @@ if ! getent hosts "$HOSTNAME" &>/dev/null; then
echo "$IP $HOSTNAME" | sudo tee -a /etc/hosts >/dev/null
echo "Added $HOSTNAME -> $IP to /etc/hosts (container DNS didn't resolve it)"
else
- echo "FAIL: $HOSTNAME does not resolve. Check cloudflared tunnel DNS routing."
+ echo "FAIL: $HOSTNAME does not resolve. Check tunnel/DNS routing."
# Stop here
fi
fi
@@ -177,7 +177,7 @@ FILENAME=$(echo "$URL" | sed 's|https\?://||; s|/|--|g; s|--$||; s|[^a-zA-Z0-9._
agent-browser screenshot ".claude/screenshots/${FILENAME}.png"
```
-Example: `https://my-app.oh-local.localhost:8443/guide/exposure/` → `.claude/screenshots/my-app.oh-local.localhost-8443--guide--exposure.png`
+Example: `https://my-app.oh-local.localhost:8443/guide/configuration/` → `.claude/screenshots/my-app.oh-local.localhost-8443--guide--configuration.png`
### Step 5 — Report
diff --git a/.claude/skills/harness-audit/SKILL.md b/.claude/skills/harness-audit/SKILL.md
index d2bc16d..0dba6d7 100644
--- a/.claude/skills/harness-audit/SKILL.md
+++ b/.claude/skills/harness-audit/SKILL.md
@@ -176,8 +176,6 @@ Launch 4 Agent tool calls **in a single message**. Each receives the Context Sna
>
> 4. **State corruption risks** — Look for: shared files written by multiple agents concurrently (e.g., `MEMORY.md`), no file locking on append operations, mid-commit crash scenarios (partial writes to critical files), compose volumes that could diverge.
>
-> 5. **Tunnel fragility** — Check `.claude/skills/cloudflared-tunnel/SKILL.md` and any compose services named `tunnel` or `cloudflared`. Is there auto-restart? Health monitoring? What happens if the tunnel drops silently?
->
> **Return format (Ultra compression):**
> ```
> CRITIC_FINDINGS
@@ -282,7 +280,7 @@ See `context/rules/memory.md` for the canonical Memory Improvement Protocol.
|---------|--------------|
| PM | Onboarding, skill consistency, issue templates, wiki/memory utilization |
| Implementer | Startup reliability, test coverage, CI/CD, package health, compose overlays |
-| Critic | Security, heartbeat reliability, worktree cleanup, state corruption, tunnel |
+| Critic | Security, heartbeat reliability, worktree cleanup, state corruption |
| Explorer | Memory quality, wiki utilization, heartbeat health, worktree status, skill usage |
### Severity and effort definitions
diff --git a/.claude/skills/strategic-proposal/SKILL.md b/.claude/skills/strategic-proposal/SKILL.md
index ca068d8..075db14 100644
--- a/.claude/skills/strategic-proposal/SKILL.md
+++ b/.claude/skills/strategic-proposal/SKILL.md
@@ -82,7 +82,7 @@ Assemble a structured markdown briefing to pass to ALL 5 experts:
- API routes: none
### Infrastructure
-- Docker Compose + PostgreSQL 16 + cloudflared tunnel
+- Docker Compose + opt-in PostgreSQL 16 overlay
- CI/CD: GitHub Actions (lint, format, type-check, build, test, E2E)
- Release: CalVer → GHCR Docker image
- Agent: 8 skills, 7 sub-agents, 4 heartbeats, memory protocol
diff --git a/.devcontainer/docker-compose.postgres.yml b/.devcontainer/docker-compose.postgres.yml
new file mode 100644
index 0000000..c6be848
--- /dev/null
+++ b/.devcontainer/docker-compose.postgres.yml
@@ -0,0 +1,49 @@
+# PostgreSQL Service Overlay
+# ===========================
+# Purpose: Run PostgreSQL 16 as a sibling service for sandbox database operations.
+# Default credentials: user=sandbox, password=sandbox, database=sandbox
+# Network: Sandbox and postgres share isolated network (devnet) for inter-service communication
+# Storage: pgdata volume persists database across container restarts
+# Health: Includes healthcheck; sandbox waits for postgres to be ready before starting
+# Environment vars injected into sandbox: DATABASE_URL, PGHOST, PGUSER, PGPASSWORD, PGDATABASE
+# Usage: docker compose -f docker-compose.yml -f docker-compose.postgres.yml up
+# Connection: psql postgresql://sandbox:sandbox@postgres:5432/sandbox (from inside sandbox)
+
+services:
+ postgres:
+ image: postgres:16-alpine
+ container_name: ${SANDBOX_NAME:-openharness}-postgres
+ environment:
+ POSTGRES_USER: sandbox
+ POSTGRES_PASSWORD: sandbox
+ POSTGRES_DB: sandbox
+ volumes:
+ - pgdata:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U sandbox"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ networks:
+ - devnet
+
+ sandbox:
+ depends_on:
+ postgres:
+ condition: service_healthy
+ environment:
+ - DATABASE_URL=postgresql://sandbox:sandbox@postgres:5432/sandbox
+ - PGHOST=postgres
+ - PGUSER=sandbox
+ - PGPASSWORD=sandbox
+ - PGDATABASE=sandbox
+ networks:
+ - devnet
+
+networks:
+ devnet:
+ name: ${SANDBOX_NAME:-openharness}-devnet
+ driver: bridge
+
+volumes:
+ pgdata:
diff --git a/AGENTS.md b/AGENTS.md
index 3ccdb9e..33f9d96 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -107,7 +107,6 @@ Remove the sandbox.
|-------|------|
| `/release` | CalVer release — branch, tag, push, GHCR |
| `/ci-status` | After `git push` — poll CI, report pass/fail |
-| `/cloudflared-tunnel` | One-time named tunnel setup (remote mode) |
| `/agent-browser` | Open a URL headless for screenshots / preview checks |
| `/prd` | Generate a new PRD from a feature description |
| `/ralph` | Convert markdown PRD → `tasks//prd.json` for the Ralph runner |
@@ -122,10 +121,10 @@ the `Lifecycle` section above. There is no dedicated skill.
## Exposing apps
-There is no first-class exposure tool right now. For external access,
-either enable the `cloudflared` compose overlay (see
-`docs/integrations/cloudflare.md`) or stand up your own reverse proxy
-in front of the sandbox.
+There is no first-class exposure tool. For external access, stand up
+your own reverse proxy (nginx/Caddy/Traefik) or tunnel (cloudflared,
+ngrok, tailscale-funnel) in front of the sandbox — the base ships
+without any of these.
Long-running apps inside the sandbox go in named tmux sessions, related
apps as stacked panes — see `context/rules/sandbox-processes.md`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c7e1c9..fa44da8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ Update policy and release automation live in [`.claude/rules/git.md`](.claude/ru
## [Unreleased]
### Removed
+- In-tree docker-compose overlays under `.devcontainer/` except `docker-compose.postgres.yml`: the five host-auth overlays (`claude-host.yml`, `codex-host.yml`, `opencode-host.yml`, `pi-host.yml`, `deepagents-host.yml`), the three SSH overlays (`ssh.yml`, `ssh-generate.yml`, `sshd.yml`), `git.yml`, and `cloudflared.yml`. The host-auth overlays existed to share host agent credentials when the host UID matched the sandbox's UID 1000; `.devcontainer/entrypoint.sh` now syncs the sandbox user's UID/GID to the bind-mounted repo's owner on boot (replacing the host-group-membership workaround), so files written in either direction stay readable without per-agent overlays. Agent auth still persists in named volumes (`claude-auth`, `codex-auth`, `pi-auth`, `opencode-auth`, `deepagents-auth`, `cloudflared-auth`, `gh-config`); `gh auth login` covers git over HTTPS without per-overlay setup. Postgres remains opt-in via `docker-compose.postgres.yml`. Removed companion infrastructure: `install/cloudflared-tunnel.sh`, the `/cloudflared-tunnel` skill, the `*_HOST_BIND_MOUNT` / `SANDBOX_PASSWORD` / `INSTALL_BROWSER` / `INSTALL_CLOUDFLARED` / `GIT_COMMON_DIR` / `HOST_SSH_DIR` / `HOST_*_DIR` env plumbing in `.devcontainer/entrypoint.sh`, and the host-UID overlay-stripping block in `scripts/install.sh`. Deleted doc: `docs/integrations/cloudflare.md`. The `composeOverrides[]` mechanism in `config.json` is retained so downstream packs (Pi extensions, BYO harness packs) can still register their own overlays. ([#283](https://github.com/ryaneggz/open-harness/issues/283))
- Orphaned `.pi/onboard-steps/slack.ts` (and the empty `.pi/onboard-steps/` directory). The wizard logic lives in `apps/oh/src/config/slack.ts` now, with the bugs from the original mifune port fixed (correct tmux session name `agent-pi`, real `pi` launch path, token-prefix validation, allowlist-ID validation, atomic env-file merge). ([#281](https://github.com/ryaneggz/open-harness/issues/281))
- Stale documentation pages: `docs/onboarding.md`, `docs/sandbox-lifecycle.md`, `docs/connecting.md`, `docs/operations/{provision,destroy,repair}.md`, and `docs/architecture/{overview,container-runtime,crons-and-heartbeats}.md`. These pages described flows and tooling that no longer match the harness; rather than rewrite them, the docs site is trimmed to `intro`, `quickstart`, `installation`, and the `guide` / `agents` / `integrations` sections as the smaller accurate foundation. `context/rules/repo-layout-source.md` is also removed — it pinned `docs/architecture/container-runtime.md#repo-layout` as the canonical repo-tree source; with that file gone, the rule has no anchor. Cross-references in `AGENTS.md` (which is `CLAUDE.md` via symlink), `README.md`, `docs/intro.md`, `tasks/README.md`, `apps/README.md`, `crons/README.md`, `.claude/skills/harness-context/SKILL.md`, `context/rules/directory-readme.md`, `context/rules/memory.md`, `context/README.md`, `context/rules/README.md`, `.devcontainer/.example.env`, and `apps/docs/src/pages/index.tsx` updated to point at surviving docs or removed where they no longer apply. The brief `## Project Structure` block in `AGENTS.md` plus per-directory `README.md` files now stand in for the deleted repo-layout tree. ([#279](https://github.com/ryaneggz/open-harness/issues/279))
- `.pi/overlays/docker-compose.slack.yml` and all references to it. The in-tree Pi extension at `.pi/extensions/slack/` reads `SLACK_APP_TOKEN` / `SLACK_BOT_TOKEN` directly from `process.env` at `session_start`, so the compose-time wiring overlay is redundant. `docs/integrations/slack.md`, `README.md`, `docs/architecture/container-runtime.md`, `docs/guide/bring-your-own-harness.md`, `.devcontainer/docker-compose.yml`, and `.devcontainer/.example.env` no longer reference the overlay; `.pi/overlays/` directory removed. Source of truth for Slack env wiring is `.pi/extensions/slack/README.md`.
diff --git a/Makefile b/Makefile
index 1b638c9..eb67601 100644
--- a/Makefile
+++ b/Makefile
@@ -5,26 +5,19 @@
SANDBOX_NAME ?= openharness
COMPOSE_BASE := -f .devcontainer/docker-compose.yml
-COMPOSE_OVERRIDES := $(shell jq -r '.composeOverrides[]?' \
- config.json 2>/dev/null | sed 's|^|-f |' | tr '\n' ' ')
+# Base ships with no required overlays. Downstream packs (Pi extensions,
+# BYO harness packs) register their own by appending paths to
+# composeOverrides[] in config.json; jq is only invoked when both jq and
+# config.json are present.
+COMPOSE_OVERRIDES := $(shell command -v jq >/dev/null 2>&1 && [ -f config.json ] && \
+ jq -r '.composeOverrides[]?' config.json 2>/dev/null | sed 's|^|-f |' | tr '\n' ' ')
COMPOSE := docker compose $(COMPOSE_BASE) $(COMPOSE_OVERRIDES)
.DEFAULT_GOAL := help
-.PHONY: sandbox shell destroy stop logs ps restart help _check-jq _check-config
+.PHONY: sandbox shell destroy stop logs ps restart help
-_check-jq:
- @command -v jq >/dev/null 2>&1 || { \
- echo "ERROR: jq is required to expand config.json composeOverrides."; \
- echo "Install jq (e.g. apt install jq, brew install jq) and re-run."; \
- exit 1; }
-
-_check-config:
- @[ -f config.json ] || { \
- echo "ERROR: config.json missing. Run scripts/install.sh, OR: cp config.example.json config.json"; \
- exit 1; }
-
-sandbox: _check-jq _check-config ## Provision and start the sandbox
+sandbox: ## Provision and start the sandbox
$(COMPOSE) up -d --build
shell: ## Connect to the sandbox (agent choice happens inside)
diff --git a/README.md b/README.md
index 8770971..6a0ec4b 100644
--- a/README.md
+++ b/README.md
@@ -42,8 +42,8 @@ Prefer VS Code or remote SSH? Use the Dev Containers extension's "Attach to Runn
`.devcontainer/.env` is generated with safe defaults during install. Open
it any time to change `SANDBOX_NAME`, set a different `GH_TOKEN`, change
-`TZ`, set `SANDBOX_PASSWORD` (sshd overlay), or set `SLACK_*` tokens for
-the Pi Slack extension. Apply with `make destroy && make sandbox`.
+`TZ`, or set `SLACK_*` tokens for the Pi Slack extension. Apply with
+`make destroy && make sandbox`.
Manual setup (no installer)
diff --git a/apps/docs/src/pages/index.tsx b/apps/docs/src/pages/index.tsx
index b9fbaa6..3dd69f4 100644
--- a/apps/docs/src/pages/index.tsx
+++ b/apps/docs/src/pages/index.tsx
@@ -82,7 +82,7 @@ const WHY: Array<{ title: string; body: string }> = [
},
{
title: "Composable substrate",
- body: "Postgres, Cloudflare tunnels, SSH, the Caddy gateway — cherry-pick via Compose overlays. Multi-agent setups ship as separate harness packs.",
+ body: "Postgres ships as an opt-in compose overlay; add tunnels, reverse proxies, or multi-agent setups as harness packs.",
},
];
@@ -185,7 +185,7 @@ export default function Home(): React.ReactElement {
Open Harness runs as a single long-lived Docker container dedicated to your project. The bind-mounted workspace/ houses whatever your project needs — one repo, several, side-by-side branches, scratch dirs. The agent owns its workspace; your laptop stays clean.
- A markdown cron runtime reads crons/*.md and wakes the agent on a schedule — issue triage, PR review, background grooming, anything you want running while you sleep. Configure the sandbox via .devcontainer/.env; add infra (Postgres, Cloudflare tunnels, SSH, Caddy gateway) via Compose overlays.
+ A markdown cron runtime reads crons/*.md and wakes the agent on a schedule — issue triage, PR review, background grooming, anything you want running while you sleep. Configure the sandbox via .devcontainer/.env; Postgres ships as an opt-in compose overlay, and additional infra (tunnels, reverse proxies) is registered via harness-pack overlays in config.json.
Multi-agent setups — like a Pi+Mom Slack bot — ship as separate harness packs you git clone into the workspace.
@@ -220,7 +220,7 @@ export default function Home(): React.ReactElement {
Documentation
- Quickstart, architecture, overlays
+ Quickstart, architecture, agents
diff --git a/context/rules/git.md b/context/rules/git.md
index fe559d3..7567c5e 100644
--- a/context/rules/git.md
+++ b/context/rules/git.md
@@ -34,13 +34,6 @@ Detect via `git show-ref --verify --quiet refs/heads/` (or remote `refs/re
Inside sandbox, run `gh auth login && gh auth setup-git` during onboarding. GitHub CLI installs credential helper — `git push` / `git fetch` use your GitHub token — no SSH keys required.
-Advanced opt-ins (only when git-over-SSH explicitly needed):
-
-- `docker-compose.ssh.yml` — mounts host `~/.ssh` read-only (reuses existing keys)
-- `docker-compose.ssh-generate.yml` — generates persistent ED25519 keypair in named volume (add to GitHub manually)
-
-Mutually exclusive — pick at most one. Both off by default.
-
## PR Titles
Format: `FROM TO ` (literal)
diff --git a/docs/agents/deepagents.md b/docs/agents/deepagents.md
index 5cc5f92..e7f4c04 100644
--- a/docs/agents/deepagents.md
+++ b/docs/agents/deepagents.md
@@ -61,15 +61,6 @@ on PATH and **configured** only when `~/.deepagents/.env` or
`~/.deepagents/config.toml` is non-empty — an empty mounted directory is
not treated as authenticated.
-### Reusing host DeepAgents state
-
-If you already use DeepAgents on the host, opt in to the
-`deepagents-host` overlay to bind-mount your host `~/.deepagents` into the
-sandbox. The overlay requires host UID 1000 and a pre-existing host
-`~/.deepagents` directory; DeepAgents stores raw provider API keys with no
-scope or expiry, so enable it only when you've accepted that blast radius
-explicitly.
-
## State persistence and repo-local `.deepagents/`
Two separate directories carry DeepAgents state, with very different
@@ -77,7 +68,7 @@ durability and review semantics:
| Path | Scope | Persistence | Notes |
|---|---|---|---|
-| `~/.deepagents/` | Per-sandbox user state | Survives rebuilds via the `deepagents-auth` volume (or host bind-mount) | Provider keys, model defaults, memory, skills, sessions live here. **Only place secrets here.** |
+| `~/.deepagents/` | Per-sandbox user state | Survives rebuilds via the `deepagents-auth` volume | Provider keys, model defaults, memory, skills, sessions live here. **Only place secrets here.** |
| `/.deepagents/` | Per-project | Whatever git decides | Project memory and skills the agent may load from the workspace root. **Treat as project data — follows normal `.gitignore` and code-review rules.** |
A repo-local `.deepagents/` directory may be read by the agent and may be
@@ -156,8 +147,7 @@ Two environment overrides apply:
> via `RALPH_DEEPAGENTS_FLAGS` grants unrestricted non-interactive shell
> execution. Combined with the mounted Docker socket (enabled by default
> in the base compose file), this can affect sibling containers or the
-> host Docker daemon, and combined with the `deepagents-host` overlay can
-> reach raw host provider keys. Only use `all` for trusted tasks where
+> host Docker daemon. Only use `all` for trusted tasks where
> you have accepted that risk explicitly.
## Tips
diff --git a/docs/agents/opencode.md b/docs/agents/opencode.md
index 4f96a5c..8b297d4 100644
--- a/docs/agents/opencode.md
+++ b/docs/agents/opencode.md
@@ -50,10 +50,6 @@ tmux new-session -d -s agent-opencode 'opencode'
tmux attach -t agent-opencode
```
-## Host auth overlay
-
-The `opencode-host` overlay bind-mounts your host OpenCode state from `~/.local/share/opencode` into the sandbox. Enable it only for trusted workflows, because any code running in the sandbox can read those credentials.
-
## Upstream documentation
- [OpenCode CLI](https://opencode.ai/docs/cli/)
diff --git a/docs/installation.md b/docs/installation.md
index 4688fcd..ecaa34d 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -26,7 +26,7 @@ The installer:
1. Verifies Docker and git are present.
2. Clones the repo into `~/.openharness` (or pulls latest if the directory already exists).
-3. Prompts for `SANDBOX_NAME` and `SANDBOX_PASSWORD`, then writes `.devcontainer/.env`.
+3. Prompts for `SANDBOX_NAME`, then writes `.devcontainer/.env`.
4. Runs `docker compose -f .devcontainer/docker-compose.yml up -d --build`.
5. Prints the next-step commands (open a shell, stop, tear down).
@@ -37,9 +37,8 @@ The installer:
| `OH_INSTALL_REF=` | Pin the cloned repo to a specific tag or SHA instead of `main`. |
| `OH_ASSUME_YES=1` | Accept defaults at every prompt. |
| `SANDBOX_NAME=` | Skip the "Container name" prompt. |
-| `SANDBOX_PASSWORD=` | Skip the credential prompt (used by the optional `sshd` overlay). |
-`SANDBOX_NAME` and `SANDBOX_PASSWORD` resolve independently — set one and the other still prompts. Both fall back to defaults (`openharness` and `changeme`) when no TTY is available.
+`SANDBOX_NAME` falls back to the default (`openharness`) when no TTY is available.
## Manual installation
@@ -112,9 +111,8 @@ Debian Bookworm (slim). The `sandbox` user has passwordless sudo.
| Tool | Purpose |
|------|---------|
-| Docker CLI + Compose | Container management from inside the sandbox (with docker overlay) |
+| Docker CLI + Compose | Container management from inside the sandbox (host docker socket bind-mounted by the base compose) |
| GitHub CLI (`gh`) | PRs, issues, releases from the terminal |
-| cloudflared | Cloudflare Tunnel for public URLs to dev servers |
| tmux | Detachable terminal sessions for long-running agents |
| croner | Markdown-frontmatter cron scheduler for autonomous agent tasks |
@@ -127,7 +125,7 @@ Debian Bookworm (slim). The `sandbox` user has passwordless sudo.
| ripgrep (`rg`) | Fast code search |
| curl, wget | HTTP clients |
| nano | Text editor |
-| openssh-server | SSH server (enabled via sshd overlay) |
+| openssh-client | `ssh-keygen` for GitHub auth flows |
| bash-completion | Tab completion |
### Shell aliases
@@ -143,10 +141,12 @@ codex → codex --dangerously-bypass-approvals-and-sandbox
Auth credentials survive container rebuilds via named Docker volumes:
-- `claude-auth` → `~/.claude` (Claude Code OAuth) — or, with the `claude-host` overlay, a RW bind-mount of your host `~/.claude`.
-- `codex-auth` → `~/.codex` (Codex OAuth) — or, with the `codex-host` overlay, a RW bind-mount of your host `~/.codex`.
-- `opencode-auth` → `~/.local/share/opencode` (OpenCode OAuth; `auth.json`) — or, with the `opencode-host` overlay, a RW bind-mount of your host `~/.local/share/opencode`.
-- `pi-auth` → `~/.pi` (Pi Agent OAuth) — or, with the `pi-host` overlay, a RW bind-mount of your host `~/.pi`.
-- `deepagents-auth` → `~/.deepagents` (DeepAgents provider keys, memory, skills, sessions) — or, with the `deepagents-host` overlay, a RW bind-mount of your host `~/.deepagents`. Repo-local `.deepagents/` is **project data** and follows normal `.gitignore` and code-review rules — never put secrets there.
-- `cloudflared-auth` → `~/.cloudflared` (Cloudflare credentials)
+- `claude-auth` → `~/.claude` (Claude Code OAuth)
+- `codex-auth` → `~/.codex` (Codex OAuth)
+- `opencode-auth` → `~/.local/share/opencode` (OpenCode OAuth; `auth.json`)
+- `pi-auth` → `~/.pi` (Pi Agent OAuth)
+- `deepagents-auth` → `~/.deepagents` (DeepAgents provider keys, memory, skills, sessions). Repo-local `.deepagents/` is **project data** and follows normal `.gitignore` and code-review rules — never put secrets there.
+- `cloudflared-auth` → `~/.cloudflared` (Cloudflare tunnel credentials, when used)
- `gh-config` → `~/.config/gh` (GitHub CLI tokens)
+
+Downstream harness packs and Pi extensions can introduce additional volumes or bind-mount overlays by adding their own compose file to `composeOverrides[]` in `config.json`.
diff --git a/docs/integrations/cloudflare.md b/docs/integrations/cloudflare.md
deleted file mode 100644
index 48fc44d..0000000
--- a/docs/integrations/cloudflare.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-sidebar_position: 3
-title: "Cloudflare"
----
-
-# Cloudflare
-
-Open Harness exposes sandbox apps to the public internet via **Cloudflare named tunnels**. Use the `/cloudflared-tunnel` skill (or run `install/cloudflared-tunnel.sh` directly) to create a tunnel, write its ingress config, and route DNS — all in one idempotent step.
-
-## Enabling the cloudflared overlay
-
-To install `cloudflared` inside the sandbox, add the cloudflared overlay at startup:
-
-```bash
-docker compose \
- -f .devcontainer/docker-compose.yml \
- -f .devcontainer/docker-compose.cloudflared.yml \
- up -d --build
-```
-
-This sets `INSTALL_CLOUDFLARED=true` in the container environment. The entrypoint installs the `cloudflared` binary to `/usr/local/bin/cloudflared` during container startup.
-
-## Named tunnels via cloudflared-tunnel.sh
-
-For persistent public URLs tied to a Cloudflare-managed domain, use `install/cloudflared-tunnel.sh`. This script creates a named tunnel, writes the ingress config, and routes DNS — all idempotently. The `/cloudflared-tunnel` skill drives this same script with the canonical onboarding flow (prereq checks, multi-ingress YAML, DNS routing, optional `--run`, post-start verification).
-
-### Prerequisites
-
-1. `cloudflared` installed (provided by the cloudflared overlay above).
-2. Authenticated with Cloudflare: `cloudflared login` (opens browser, saves `~/.cloudflared/cert.pem`).
-3. A domain managed in your Cloudflare account.
-
-### Usage
-
-Single ingress:
-
-```bash
-install/cloudflared-tunnel.sh
-```
-
-Multiple ingress routes:
-
-```bash
-install/cloudflared-tunnel.sh : [: ...]
-```
-
-Start the tunnel immediately after configuration:
-
-```bash
-install/cloudflared-tunnel.sh myproject app.example.com:3000 api.example.com:3001 --run
-```
-
-Replace `example.com` with your own Cloudflare-managed domain.
-
-### Running the tunnel in tmux
-
-For a long-lived tunnel, run it in a named tmux session:
-
-```bash
-tmux new-session -d -s expose-public-3000 \
- 'cloudflared tunnel run myproject 2>&1 | tee /tmp/expose-public-3000.log'
-```
-
-Attach to inspect logs:
-
-```bash
-tmux attach -t expose-public-3000
-```
-
-### Driving the flow with the skill
-
-Prefer the `/cloudflared-tunnel` skill when working interactively — it parses arguments, checks prerequisites, creates or reuses the tunnel, writes `~/.cloudflared/config-.yml`, routes DNS for every hostname, and (with `--run`) starts the tunnel and verifies each hostname end-to-end. See `.claude/skills/cloudflared-tunnel/SKILL.md` for the canonical step-by-step.
diff --git a/docs/integrations/github.md b/docs/integrations/github.md
index 84db26e..2536058 100644
--- a/docs/integrations/github.md
+++ b/docs/integrations/github.md
@@ -40,26 +40,6 @@ gh run list --branch feat/my-feature
gh issue view 42
```
-## Optional SSH overlays
-
-For cases where Git-over-SSH is required (custom Git servers, deploy keys, or workflows that do not support HTTPS), two compose overlays are available:
-
-| Overlay | Behavior |
-|---|---|
-| `docker-compose.ssh.yml` | Mounts your host `~/.ssh` directory into the sandbox read-only. Uses your existing SSH keys without copying them. |
-| `docker-compose.ssh-generate.yml` | Generates a new persistent ED25519 keypair in a named Docker volume. The public key must be added to GitHub manually before use. |
-
-These overlays are mutually exclusive — use at most one. Both are off by default. Add one when starting the sandbox:
-
-```bash
-docker compose \
- -f .devcontainer/docker-compose.yml \
- -f .devcontainer/docker-compose.ssh.yml \
- up -d --build
-```
-
-For most users, `gh auth login` and `gh auth setup-git` are sufficient and no SSH overlay is needed.
-
## Persisting credentials across restarts
-The `gh` token is stored inside the container at `~/.config/gh/`. Because this path is inside the container, it is lost when the container is removed. To persist credentials across `docker compose down -v` cycles, either re-run `gh auth login` after each rebuild, or bind-mount a host directory to `~/.config/gh/` via a custom compose overlay.
+The `gh` token is stored inside the container at `~/.config/gh/`, backed by the named `gh-config` volume. The token survives `docker compose down` and `docker compose up` cycles. `docker compose down -v` removes the volume — re-run `gh auth login` after a `down -v`, or register your own compose overlay in `config.json` `composeOverrides[]` to bind-mount a host directory to `~/.config/gh/`.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 63fefbd..e845374 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -70,7 +70,10 @@ safe defaults during install. The most-edited values:
Apply changes with `make destroy && make sandbox`.
-For Postgres, Cloudflare tunnels, SSH, etc., chain compose overlays from `.devcontainer/`.
+For additional services, the base ships with an opt-in Postgres overlay
+at `.devcontainer/docker-compose.postgres.yml`; for anything else
+(tunnels, reverse proxies), register your own compose overlay by
+appending it to `composeOverrides[]` in `config.json`.
## Next steps
@@ -79,7 +82,6 @@ With the sandbox running, here's what to wire up next:
- Slack bridge for your sandbox agent → [integrations/slack.md](./integrations/slack.md)
- GitHub auth (only if you skipped `GH_TOKEN` at install) → [integrations/github.md](./integrations/github.md)
- Postgres via Compose overlay → [integrations/postgres.md](./integrations/postgres.md)
-- Cloudflare tunnels for external access → [integrations/cloudflare.md](./integrations/cloudflare.md)
## Tear down