Merge pull request #241 from ryaneggz/feat/237-deepagents-cli-support
FROM feat/237-deepagents-cli-support TO development
This commit is contained in:
24 files changed
+870
-72
No files matched your search
@@ -47,6 +47,15 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1
|
||||
&& cp /root/.local/bin/uv /usr/local/bin/uv \
|
||||
&& cp /root/.local/bin/uvx /usr/local/bin/uvx
|
||||
|
||||
# ─── DeepAgents CLI ────────────────────────────────────────────────
|
||||
# Install via `uv tool install` into image-level locations so the
|
||||
# `deepagents` executable is on PATH for the sandbox user. Avoid
|
||||
# user-home installs that would land in /root/.local/bin only.
|
||||
ENV UV_TOOL_DIR=/opt/uv/tools
|
||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||
RUN mkdir -p "$UV_TOOL_DIR" \
|
||||
&& uv tool install deepagents-cli
|
||||
|
||||
# ─── pnpm (via corepack) ───────────────────────────────────────────
|
||||
ENV PNPM_HOME="/usr/local/share/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# DeepAgents Host State Mount Overlay
|
||||
# =====================================
|
||||
# Purpose: Bind-mount the host's ~/.deepagents directory into the sandbox,
|
||||
# so the sandboxed `deepagents` CLI inherits host provider keys, model
|
||||
# config, memory, skills, and sessions. Trusted local users running
|
||||
# multiple sandboxes can reuse a single source of DeepAgents state instead
|
||||
# of re-pasting provider keys into each sandbox's named volume.
|
||||
#
|
||||
# One mount, one concern:
|
||||
# - ~/.deepagents/ (dir) — .env (provider keys), config.toml (model/CLI
|
||||
# defaults), memory, skills, sessions
|
||||
#
|
||||
# Pre-flight:
|
||||
# $HOST_DEEPAGENTS_DIR (default ~/.deepagents) must exist as a directory
|
||||
# on the host. If the target is missing, Docker auto-creates it as a
|
||||
# directory owned by root — `deepagents` then fails to read/write its
|
||||
# provider env file. `scripts/install.sh` pre-creates `~/.deepagents`
|
||||
# alongside `~/.claude`, `~/.codex`, and `~/.pi` so this is satisfied
|
||||
# by default for installer-driven setups.
|
||||
#
|
||||
# Requirements: Host UID MUST equal the sandbox UID (1000). Provider key
|
||||
# files under `~/.deepagents/.env` are typically mode 0600 (owner-only),
|
||||
# so the group-membership trick used elsewhere in entrypoint.sh does NOT
|
||||
# grant access. If host UID ≠ 1000, `deepagents` will fail to read the
|
||||
# provider env file despite the bind-mount succeeding;
|
||||
# `scripts/install.sh` strips this overlay from `composeOverrides` when
|
||||
# the host UID is not 1000 so non-UID-1000 hosts fall back to the
|
||||
# `deepagents-auth` named volume cleanly.
|
||||
#
|
||||
# Security tradeoff: this mounts provider API keys, agent memory, and
|
||||
# session history into a container that may execute untrusted agent code
|
||||
# AND has the host Docker socket bind-mounted by default. With
|
||||
# `--shell-allow-list all` (operator opt-in via `RALPH_DEEPAGENTS_FLAGS`),
|
||||
# DeepAgents could shell out and reach sibling containers or the host
|
||||
# Docker daemon. Enable the host overlay only for trusted workflows.
|
||||
# Provider keys are NOT OAuth tokens — they typically have no expiry and
|
||||
# no per-request scope, so a leak from this overlay has a wider blast
|
||||
# radius than `claude-host` or `codex-host`. The overlay is opt-in: it is
|
||||
# NOT listed in the default `.openharness/config.json` `composeOverrides`.
|
||||
#
|
||||
# Bleed-through: ~/.deepagents/sessions/, memory, and skills from other
|
||||
# host work will be visible inside the sandbox. Acknowledged cost of
|
||||
# full-mount convenience.
|
||||
#
|
||||
# Permissions: Read-write (sandbox writes host sessions/memory live).
|
||||
# Replaces: deepagents-auth named volume from base docker-compose.yml at
|
||||
# the /home/sandbox/.deepagents target path. The named volume declaration
|
||||
# stays in the base compose — it becomes orphaned (unmounted) while this
|
||||
# overlay is active and is reinstated when the overlay is removed. To
|
||||
# return to the named volume, remove this overlay from
|
||||
# `.openharness/config.json` `composeOverrides` and rerun `make sandbox`
|
||||
# (or `make destroy && make sandbox` if the container is already up).
|
||||
# Resetting the `deepagents-auth` named volume only (without touching
|
||||
# host `~/.deepagents`) is `docker volume rm <project>_deepagents-auth`.
|
||||
#
|
||||
# Entrypoint coordination: sets DEEPAGENTS_HOST_BIND_MOUNT=1 so
|
||||
# entrypoint.sh skips its recursive chown on /home/sandbox/.deepagents
|
||||
# (which would otherwise rewrite host file ownership).
|
||||
#
|
||||
# Usage: enable via .openharness/config.json composeOverrides[]:
|
||||
# ".devcontainer/docker-compose.deepagents-host.yml"
|
||||
|
||||
services:
|
||||
sandbox:
|
||||
volumes:
|
||||
- ${HOST_DEEPAGENTS_DIR:-~/.deepagents}:/home/sandbox/.deepagents
|
||||
environment:
|
||||
- DEEPAGENTS_HOST_BIND_MOUNT=1
|
||||
@@ -11,6 +11,11 @@
|
||||
# - codex-auth:/home/sandbox/.codex — Persistent Codex CLI auth
|
||||
# - pi-auth:/home/sandbox/.pi — Persistent Pi Agent (oh/pi CLI) auth
|
||||
# - opencode-auth:/home/sandbox/.local/share/opencode — Persistent OpenCode auth
|
||||
# - deepagents-auth:/home/sandbox/.deepagents — Persistent DeepAgents CLI state
|
||||
# (provider keys, model config, memory, skills, sessions). v1 persists
|
||||
# ONLY this path; repo-local .deepagents/ under the bind-mounted project
|
||||
# root is project data and follows normal git review/ignore rules — do
|
||||
# not place secrets there.
|
||||
# - cloudflared-auth:/home/sandbox/.cloudflared — Cloudflare credentials
|
||||
# - gh-config:/home/sandbox/.config/gh — GitHub CLI config
|
||||
#
|
||||
@@ -48,6 +53,7 @@ services:
|
||||
- codex-auth:/home/sandbox/.codex
|
||||
- pi-auth:/home/sandbox/.pi
|
||||
- opencode-auth:/home/sandbox/.local/share/opencode
|
||||
- deepagents-auth:/home/sandbox/.deepagents
|
||||
- cloudflared-auth:/home/sandbox/.cloudflared
|
||||
- gh-config:/home/sandbox/.config/gh
|
||||
extra_hosts:
|
||||
@@ -73,5 +79,6 @@ volumes:
|
||||
codex-auth:
|
||||
pi-auth:
|
||||
opencode-auth:
|
||||
deepagents-auth:
|
||||
cloudflared-auth:
|
||||
gh-config:
|
||||
@@ -13,12 +13,12 @@ if [ -S "$SOCK" ]; then
|
||||
fi
|
||||
|
||||
# Fix ownership of mounted volumes (created as root by Docker).
|
||||
# Skip .claude / .codex / .pi / opencode when their host-mount overlays are active —
|
||||
# they're host bind-mounts and chown would rewrite host file ownership
|
||||
# (see docker-compose.{claude,codex,pi,opencode}-host.yml).
|
||||
# Skip .claude / .codex / .pi / .deepagents / opencode when their host-mount
|
||||
# overlays are active — they're host bind-mounts and chown would rewrite
|
||||
# host file ownership (see docker-compose.{claude,codex,pi,deepagents,opencode}-host.yml).
|
||||
# Harness packs that introduce their own host-mount overlays should add
|
||||
# their own skip clauses or run a *-entrypoint-hook.sh.
|
||||
for dir in .claude .codex .pi .cloudflared .config/gh .ssh .openharness; do
|
||||
for dir in .claude .codex .pi .deepagents .cloudflared .config/gh .ssh .openharness; do
|
||||
if [ -d "/home/sandbox/$dir" ]; then
|
||||
if [ "$dir" = ".claude" ] && [ "${CLAUDE_HOST_BIND_MOUNT:-0}" = "1" ]; then
|
||||
continue
|
||||
@@ -29,6 +29,9 @@ for dir in .claude .codex .pi .cloudflared .config/gh .ssh .openharness; do
|
||||
if [ "$dir" = ".pi" ] && [ "${PI_HOST_BIND_MOUNT:-0}" = "1" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ "$dir" = ".deepagents" ] && [ "${DEEPAGENTS_HOST_BIND_MOUNT:-0}" = "1" ]; then
|
||||
continue
|
||||
fi
|
||||
chown -R sandbox:sandbox "/home/sandbox/$dir" 2>/dev/null || true
|
||||
[ "$dir" = ".ssh" ] && chmod 700 "/home/sandbox/$dir" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
@@ -10,6 +10,7 @@ Update policy and release automation live in [`.claude/rules/git.md`](.claude/ru
|
||||
|
||||
### Added
|
||||
- OpenCode as a core sandbox agent: install `opencode-ai` in the base image, persist `~/.local/share/opencode`, add the `opencode-host` overlay, support `scripts/ralph.sh --harness=opencode`, and document OpenAI OAuth as the default OpenCode provider path.
|
||||
- DeepAgents CLI support as a first-class supported sandbox runtime alongside Claude Code, Codex, OpenCode, and Pi. `.devcontainer/Dockerfile` installs `deepagents-cli` via `uv tool install` into `/usr/local/bin`; `.devcontainer/docker-compose.yml` adds a `deepagents-auth` named volume at `/home/sandbox/.deepagents` so provider keys, memory, skills, and sessions persist across rebuilds; `.devcontainer/docker-compose.deepagents-host.yml` is an opt-in host-state overlay (off by default — DeepAgents stores raw provider API keys with no scope/expiry, so the blast radius is wider than the Claude/Codex host overlays); `.devcontainer/entrypoint.sh` extends its chown loop to include `.deepagents` and skips it when `DEEPAGENTS_HOST_BIND_MOUNT=1`. `scripts/ralph.sh` accepts `--harness=deepagents` (or `RALPH_HARNESS=deepagents`) and dispatches to a constrained default invocation `deepagents -y --shell-allow-list recommended -q --no-stream --max-turns "$RALPH_DEEPAGENTS_MAX_TURNS" -n "$task"` (default 25 turns); `RALPH_DEEPAGENTS_FLAGS` overrides the safety/output flag string, and `--max-turns` is always appended after so the per-iteration cap cannot be silently dropped. `--shell-allow-list all` combined with the sandbox's mounted Docker socket can affect sibling containers or host Docker — only enable for trusted tasks via the env-var override. `install/banner.sh` reports DeepAgents `[installed]` from `command -v deepagents` and `[configured]` only when `~/.deepagents/.env` or `~/.deepagents/config.toml` is non-empty (an empty mounted directory is no longer treated as authenticated). New agent doc at `docs/agents/deepagents.md`; `docs/agents/overview.md`, `README.md`, `docs/quickstart.md`, `docs/onboarding.md`, `docs/installation.md`, `docs/operations/{provision,destroy,repair}.md`, `docs/guide/{overlays,permissions,configuration}.md`, and `docs/architecture/{container-runtime,overview}.md` enumerate the new agent, volume, overlay, and Ralph harness option. Repo-local `.deepagents/` is project data and follows normal git review/ignore rules; secrets and provider keys belong only in `~/.deepagents` or ignored local files. Claude remains the default Ralph harness; existing Claude/Pi/Codex execution and fallback behavior are unchanged. ([#237](https://github.com/ryaneggz/open-harness/issues/237))
|
||||
- `docs/agents/pi.md` stub (linking upstream to `badlogic/pi-mono`) and a Pi entry in the home-page agent grid at `apps/docs/src/pages/index.tsx`, matching Pi's restoration as a default-installed CLI. ([#233](https://github.com/ryaneggz/open-harness/pull/233))
|
||||
- Top-level `Makefile` wrapping `docker compose -f .devcontainer/docker-compose.yml` so users type `make sandbox` / `make shell` / `make destroy` instead of the 50-character compose lines. Targets: `sandbox`, `shell`, `destroy`, `stop`, `logs`, `ps`, `restart`, `help` (default, self-documenting via awk). Uses `-include .devcontainer/.env` so `SANDBOX_NAME` flows through. No agent-CLI targets — the harness's value is sandbox isolation, agent choice (`claude` / `codex` / `pi`) happens inside `make shell`.
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ cold, ~30s warm). Only host dependency: [Docker](https://docs.docker.com/get-doc
|
||||
cd ~/openharness
|
||||
make shell # enter the isolated sandbox
|
||||
# inside the sandbox, launch any core agent:
|
||||
# claude # Claude Code
|
||||
# codex # OpenAI Codex CLI
|
||||
# opencode # OpenCode
|
||||
# pi # Pi Coding Agent
|
||||
# claude # Claude Code (default)
|
||||
# codex # OpenAI Codex CLI
|
||||
# opencode # OpenCode
|
||||
# pi # Pi Coding Agent
|
||||
# deepagents # LangChain DeepAgents (multi-provider)
|
||||
make destroy # stop and remove the sandbox
|
||||
make help # all targets
|
||||
```
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
title: "DeepAgents"
|
||||
---
|
||||
|
||||
# DeepAgents
|
||||
|
||||
DeepAgents is LangChain's terminal coding agent. It runs interactive
|
||||
conversational sessions and one-shot non-interactive tasks against multiple
|
||||
LLM providers, with optional shell tool use gated by an explicit allow list.
|
||||
|
||||
DeepAgents is an **optional supported runtime** in Open Harness. Claude Code
|
||||
remains the default agent path; DeepAgents is preinstalled so it can be
|
||||
selected when its provider mix or non-interactive shell-allow-list model is
|
||||
the right fit for a task.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Multi-provider agent (Anthropic, OpenAI, etc.) configurable from a single
|
||||
`~/.deepagents/.env` file.
|
||||
- Non-interactive mode (`-n "$task"`) with explicit shell-allow-list gating,
|
||||
suitable for Ralph harness loops with a constrained default tool surface.
|
||||
- Project-aware: optionally reads memory and skills from a repo-local
|
||||
`.deepagents/` directory at the workspace root.
|
||||
|
||||
## Install
|
||||
|
||||
DeepAgents is preinstalled in the base sandbox image via `uv tool install`.
|
||||
The `deepagents` shim lands on `/usr/local/bin` so it is on PATH for the
|
||||
`sandbox` user out of the box.
|
||||
|
||||
Verify the install:
|
||||
|
||||
```bash
|
||||
deepagents -v
|
||||
```
|
||||
|
||||
If the command is not found, the sandbox image is stale — rebuild with
|
||||
`make destroy && make sandbox`.
|
||||
|
||||
## Authentication and provider keys
|
||||
|
||||
DeepAgents reads provider API keys from `~/.deepagents/.env` and CLI
|
||||
defaults from `~/.deepagents/config.toml`. The directory is persisted by
|
||||
the `deepagents-auth` named volume by default, so credentials survive
|
||||
container rebuilds.
|
||||
|
||||
Create the env file on first use:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.deepagents
|
||||
cat > ~/.deepagents/.env <<'EOF'
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
OPENAI_API_KEY=sk-...
|
||||
EOF
|
||||
chmod 600 ~/.deepagents/.env
|
||||
```
|
||||
|
||||
The sandbox banner reports DeepAgents as **installed** when `deepagents` is
|
||||
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. See [Sharing host DeepAgents state](../guide/overlays.md#sharing-host-deepagents-state-deepagents-host)
|
||||
for the trust tradeoffs, UID-1000 requirement, disable flow, and how to
|
||||
reset the named volume without touching host `~/.deepagents`.
|
||||
|
||||
## State persistence and repo-local `.deepagents/`
|
||||
|
||||
Two separate directories carry DeepAgents state, with very different
|
||||
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.** |
|
||||
| `<repo>/.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
|
||||
committed to the repository — keep secrets and provider keys **only** in
|
||||
`~/.deepagents/` or in ignored local files. Never commit a `.env` to the
|
||||
repo-local `.deepagents/`.
|
||||
|
||||
v1 of Open Harness persists only `/home/sandbox/.deepagents`; the
|
||||
repo-local `.deepagents/` is project data subject to your repository's
|
||||
ordinary git rules.
|
||||
|
||||
## Common usage
|
||||
|
||||
### Interactive
|
||||
|
||||
Launch a conversational session:
|
||||
|
||||
```bash
|
||||
deepagents
|
||||
```
|
||||
|
||||
Run inside a dedicated tmux session so the conversation survives shell
|
||||
disconnects (per `.claude/rules/sandbox-processes.md`):
|
||||
|
||||
```bash
|
||||
tmux new-session -d -s agent-deepagents 'deepagents'
|
||||
tmux attach -t agent-deepagents
|
||||
```
|
||||
|
||||
### Non-interactive
|
||||
|
||||
Pass a task with `-n` for a single execution. By default DeepAgents
|
||||
**disables shell execution in non-interactive mode** unless an allow list
|
||||
is configured via `-S`/`--shell-allow-list` or
|
||||
`DEEPAGENTS_CLI_SHELL_ALLOW_LIST`. Open Harness defaults pick the
|
||||
`recommended` allow list, never `all`:
|
||||
|
||||
```bash
|
||||
deepagents -y --shell-allow-list recommended -n "Summarize the changes on this branch" -q --no-stream
|
||||
```
|
||||
|
||||
Flags:
|
||||
|
||||
- `-y` — assume "yes" to confirmation prompts (the sandbox is the trust
|
||||
boundary).
|
||||
- `--shell-allow-list recommended` — allow only the curated safe shell
|
||||
command set. **Do not default to `all`** — see the warning below.
|
||||
- `-n "$task"` — non-interactive single task.
|
||||
- `-q --no-stream` — quiet, buffered output for clean log capture.
|
||||
|
||||
### Ralph usage
|
||||
|
||||
`scripts/ralph.sh` accepts `deepagents` as an explicit harness:
|
||||
|
||||
```bash
|
||||
scripts/ralph.sh --harness=deepagents <task-name>
|
||||
# or
|
||||
RALPH_HARNESS=deepagents scripts/ralph.sh <task-name>
|
||||
```
|
||||
|
||||
DeepAgents is never auto-selected by Ralph fallback — it must be chosen
|
||||
explicitly. The default invocation is:
|
||||
|
||||
```bash
|
||||
deepagents -y --shell-allow-list recommended -q --no-stream --max-turns 25 -n "$task"
|
||||
```
|
||||
|
||||
Two environment overrides apply:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `RALPH_DEEPAGENTS_FLAGS` | `-y --shell-allow-list recommended -q --no-stream` | Override the flag string before the task. **Do not include `--max-turns` here** — the cap is appended separately. |
|
||||
| `RALPH_DEEPAGENTS_MAX_TURNS` | `25` | Per-call turn cap, always appended as `--max-turns "$RALPH_DEEPAGENTS_MAX_TURNS"` so a single DeepAgents call cannot hang the iteration. |
|
||||
|
||||
> **`--shell-allow-list all` warning.** Choosing `--shell-allow-list all`
|
||||
> 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
|
||||
> you have accepted that risk explicitly.
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep provider keys in `~/.deepagents/.env`. Never commit a repo-local
|
||||
`.deepagents/.env`.
|
||||
- Pair DeepAgents with a [worktree](../guide/workspace.md) so its branch
|
||||
is isolated.
|
||||
- Inspect non-interactive runs with `tmux attach -t agent-deepagents` (or
|
||||
the Ralph-launched session) to see live progress.
|
||||
|
||||
## Upstream documentation
|
||||
|
||||
- [DeepAgents CLI overview](https://docs.langchain.com/oss/python/deepagents/cli/overview)
|
||||
- [DeepAgents CLI configuration](https://docs.langchain.com/oss/python/deepagents/cli/configuration)
|
||||
- [`langchain-ai/deepagents` on GitHub](https://github.com/langchain-ai/deepagents/tree/main/libs/cli)
|
||||
+12
-9
@@ -5,18 +5,19 @@ title: "Agents Overview"
|
||||
|
||||
# Agents Overview
|
||||
|
||||
Open Harness ships with four agent CLIs preinstalled in the sandbox image: **Claude Code**, **Codex**, **OpenCode**, and **Pi**. Inside the sandbox, launch whichever you prefer and keep long-running sessions in tmux.
|
||||
Open Harness ships with five agent CLIs preinstalled in the sandbox image: **Claude Code** (default), **Codex**, **OpenCode**, **Pi**, and **DeepAgents**. Inside the sandbox, launch whichever you prefer — switch between them at any time, or keep long-running sessions in tmux.
|
||||
|
||||
The sandbox is the product; the harness is your call. To go beyond the four preinstalled options, install via `npm` / `pip` / `cargo` inside the sandbox, edit the Dockerfile, or layer in a [harness pack](../guide/bring-your-own-harness.md) like [`@ryaneggz/mifune`](https://github.com/ryaneggz/mifune) (Pi+Mom Slack bot).
|
||||
The sandbox is the product; the harness is your call. To go beyond the preinstalled options, install via `npm` / `pip` / `cargo` inside the sandbox, edit the Dockerfile, or layer in a [harness pack](../guide/bring-your-own-harness.md) like [`@ryaneggz/mifune`](https://github.com/ryaneggz/mifune) (Pi+Mom Slack bot). The product surface is one developer, one project, one harness — not racing or stacking multiple CLIs against each other.
|
||||
|
||||
## Supported agents
|
||||
|
||||
| Agent | Role | Start command | Source |
|
||||
|---|---|---|---|
|
||||
| [Claude Code](./claude-code.md) | Anthropic's terminal coding agent | `claude` | preinstalled |
|
||||
| [Claude Code](./claude-code.md) | Anthropic's terminal coding agent (default) | `claude` | preinstalled |
|
||||
| [Codex](./codex.md) | OpenAI's CLI coding agent | `codex` | preinstalled |
|
||||
| [OpenCode](./opencode.md) | Terminal coding agent with OpenAI OAuth support | `opencode` | preinstalled |
|
||||
| [Pi](./pi.md) | Lightweight, customizable harness | `pi` | preinstalled |
|
||||
| [DeepAgents](./deepagents.md) | LangChain's multi-provider terminal agent | `deepagents` | preinstalled |
|
||||
|
||||
## Verifying installation
|
||||
|
||||
@@ -25,17 +26,19 @@ claude --version
|
||||
codex --version
|
||||
opencode --version
|
||||
pi --version
|
||||
deepagents -v
|
||||
```
|
||||
|
||||
## Running multiple agents in tmux
|
||||
## Running an agent in tmux
|
||||
|
||||
Use separate tmux sessions so each agent's output stays isolated and survives disconnects:
|
||||
Use named tmux sessions so each agent's output stays isolated and survives disconnects:
|
||||
|
||||
```bash
|
||||
tmux new-session -d -s agent-claude 'claude'
|
||||
tmux new-session -d -s agent-codex 'codex --dangerously-bypass-approvals-and-sandbox'
|
||||
tmux new-session -d -s agent-opencode 'opencode'
|
||||
tmux new-session -d -s agent-pi 'pi'
|
||||
tmux new-session -d -s agent-claude 'claude'
|
||||
tmux new-session -d -s agent-codex 'codex --dangerously-bypass-approvals-and-sandbox'
|
||||
tmux new-session -d -s agent-opencode 'opencode'
|
||||
tmux new-session -d -s agent-pi 'pi'
|
||||
tmux new-session -d -s agent-deepagents 'deepagents'
|
||||
```
|
||||
|
||||
Attach to any session at any time:
|
||||
|
||||
@@ -21,7 +21,7 @@ The Dockerfile at `.devcontainer/Dockerfile` starts from `debian:bookworm-slim`.
|
||||
| Bun | Fast JS runtime and package manager |
|
||||
| uv + uvx | Python package management (for Python-based agent tasks) |
|
||||
| pnpm | Via corepack — package manager for the harness monorepo |
|
||||
| Agent CLIs | Claude Code, Pi (`oh`/`pi`), and Codex installed globally via npm |
|
||||
| Agent CLIs | Claude Code, Pi (`oh`/`pi`), and Codex installed globally via npm; DeepAgents (`deepagents`) installed via `uv tool install deepagents-cli` into `/usr/local/bin` |
|
||||
|
||||
The Dockerfile copies `.devcontainer/entrypoint.sh` into the image as `/usr/local/bin/entrypoint.sh` and sets `ENTRYPOINT ["entrypoint.sh"]`, so container startup runs `.devcontainer/entrypoint.sh` directly. That script initializes git and auth state, reconciles host/container permissions, starts the cron runtime when enabled, and only sources `install/banner.sh` from the sandbox user's shell profile for interactive prompt status.
|
||||
|
||||
@@ -50,6 +50,7 @@ The container mounts the project root and several named volumes:
|
||||
| Claude auth | named volume `claude-auth` | `/home/sandbox/.claude` | Claude Code credentials — persists across rebuilds |
|
||||
| Pi auth | named volume `pi-auth` | `/home/sandbox/.pi` | Pi Agent OAuth tokens |
|
||||
| Codex auth | named volume `codex-auth` | `/home/sandbox/.codex` | Codex credentials |
|
||||
| DeepAgents state | named volume `deepagents-auth` | `/home/sandbox/.deepagents` | DeepAgents provider keys (`.env`), CLI defaults (`config.toml`), memory, skills, sessions. v1 persists only this path; repo-local `.deepagents/` is project data subject to normal git rules. |
|
||||
| Cloudflared | named volume `cloudflared-auth` | `/home/sandbox/.cloudflared` | Cloudflare tunnel credentials |
|
||||
| GitHub CLI | named volume `gh-config` | `/home/sandbox/.config/gh` | `gh` auth tokens |
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ graph TD
|
||||
|
||||
## Key Principles
|
||||
|
||||
**One container, many agents.** All AI agent CLIs — Claude Code, Codex, plus any pack-supplied agents — share the same sandbox image built from `.devcontainer/Dockerfile`. There is no separate image per agent. Isolation is achieved through git worktrees and tmux sessions, not separate containers.
|
||||
**One container, many agents.** All AI agent CLIs — Claude Code (default), Codex, OpenCode, Pi, DeepAgents, plus any pack-supplied agents — share the same sandbox image built from `.devcontainer/Dockerfile`. There is no separate image per agent. Isolation is achieved through git worktrees and tmux sessions, not separate containers. The product surface is one developer, one project, one harness — preinstalled alternatives exist so you can pick the right tool for the task, not race them.
|
||||
|
||||
**Host stays thin.** The host only runs Docker. No host CLI, no Node runtime, no Python, and no agent toolchain is required on the developer's machine. The project root is bind-mounted into the container at `/home/sandbox/harness`, so files written inside the container are immediately visible on the host and in git.
|
||||
|
||||
|
||||
@@ -39,6 +39,24 @@ Recurring tasks are `.md` files in `crons/` at the repo root, parsed by the cron
|
||||
|----------|---------|-------------|
|
||||
| `CRON_AGENT_BIN` | `claude` | Agent binary the croner runtime invokes when a schedule fires. The body of the `.md` is passed as the prompt. |
|
||||
|
||||
### Ralph harness selection
|
||||
|
||||
`scripts/ralph.sh` accepts an explicit harness via `--harness=<name>` or `RALPH_HARNESS=<name>`. Supported values: `claude` (default), `pi`, `codex`, `deepagents`. The DeepAgents harness must be selected explicitly — it is never auto-selected by Ralph fallback.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `RALPH_HARNESS` | _(unset; falls back to `claude`)_ | Pick the active Ralph harness when the `--harness=` flag is not supplied. |
|
||||
| `RALPH_DEEPAGENTS_FLAGS` | `-y --shell-allow-list recommended -q --no-stream` | Flag string passed to `deepagents` before the task. **Do not include `--max-turns` here.** Operators can switch to `--shell-allow-list all` here when they accept unrestricted shell execution risk — see [permissions](./permissions.md#deepagents-shell-allow-list) for the trust boundary. |
|
||||
| `RALPH_DEEPAGENTS_MAX_TURNS` | `25` | Per-call turn cap appended as `--max-turns "$RALPH_DEEPAGENTS_MAX_TURNS"` so one DeepAgents call cannot hang the iteration. |
|
||||
|
||||
### DeepAgents host state (overlay)
|
||||
|
||||
Only applies when the `deepagents-host` overlay is enabled. See [overlays](./overlays.md#sharing-host-deepagents-state-deepagents-host) for trust tradeoffs and the disable flow.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HOST_DEEPAGENTS_DIR` | `~/.deepagents` | Path to your host DeepAgents directory. The overlay bind-mounts it RW into `/home/sandbox/.deepagents`. **Setting this alone does nothing** — the overlay must also be listed in `.openharness/config.json` `composeOverrides`. |
|
||||
|
||||
### SSH (overlay)
|
||||
|
||||
Only applies when an SSH overlay is enabled. See [overlays](./overlays.md#ssh-key-strategy).
|
||||
|
||||
@@ -22,6 +22,7 @@ Compose overlays in `.devcontainer/` add optional services or host-state mounts.
|
||||
| `docker-compose.codex-host.yml` | Bind-mount host `~/.codex` (auth, config) |
|
||||
| `docker-compose.opencode-host.yml` | Bind-mount host `~/.local/share/opencode` (auth) |
|
||||
| `docker-compose.pi-host.yml` | Bind-mount host `~/.pi` (auth, config) |
|
||||
| `docker-compose.deepagents-host.yml` | Bind-mount host `~/.deepagents` (provider keys, memory, sessions) — opt-in |
|
||||
| `docker-compose.gateway.yml` | [Caddy reverse-proxy sidecar](./exposure.md) (auto-activated by first `openharness expose`) |
|
||||
|
||||
## Configuration
|
||||
@@ -170,3 +171,102 @@ test -d ~/.local/share/opencode && echo OK
|
||||
```
|
||||
|
||||
Only enable host auth overlays for trusted workflows. OAuth tokens are readable by any code running in the sandbox, and sandbox writes propagate to the host directory.
|
||||
|
||||
## Sharing host DeepAgents state (`deepagents-host`)
|
||||
|
||||
By default, each sandbox stores DeepAgents CLI state — provider keys,
|
||||
model config, memory, skills, and sessions — in the `deepagents-auth`
|
||||
named Docker volume scoped to that sandbox only. The `deepagents-host`
|
||||
overlay replaces that volume with a single read-write bind-mount of
|
||||
your host state:
|
||||
|
||||
- `~/.deepagents/` (directory) — `.env` (provider API keys),
|
||||
`config.toml` (CLI defaults), memory, skills, sessions
|
||||
|
||||
The overlay is **opt-in** and is NOT listed in the default
|
||||
`.openharness/config.json` `composeOverrides`. DeepAgents itself is an
|
||||
optional supported runtime; Claude remains the default agent path.
|
||||
|
||||
Enable it by adding the overlay path to your existing
|
||||
`composeOverrides` array (do not overwrite — merge):
|
||||
|
||||
```json
|
||||
{
|
||||
"composeOverrides": [
|
||||
".devcontainer/docker-compose.cloudflared.yml",
|
||||
".devcontainer/docker-compose.claude-host.yml",
|
||||
".devcontainer/docker-compose.deepagents-host.yml"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then rebuild: `make sandbox` (or `make destroy && make sandbox` if the
|
||||
container is already up). Setting `HOST_DEEPAGENTS_DIR` alone does
|
||||
nothing — the overlay must be listed in `composeOverrides`.
|
||||
|
||||
Override the default path via `.devcontainer/.env` if your DeepAgents
|
||||
state lives elsewhere:
|
||||
|
||||
- `HOST_DEEPAGENTS_DIR` — alternate path for the directory (default `~/.deepagents`)
|
||||
|
||||
**Pre-flight — the host directory must exist before first boot.** Docker
|
||||
silently auto-creates missing bind-mount sources as **directories owned
|
||||
by root**, which the sandbox user (UID 1000) then cannot write.
|
||||
`scripts/install.sh` pre-creates `~/.deepagents` for you; if you
|
||||
provisioned without the installer, run:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.deepagents
|
||||
```
|
||||
|
||||
**Tradeoffs — only enable for trusted workflows:**
|
||||
|
||||
- **Credential blast radius (wider than `claude-host` / `codex-host`)** —
|
||||
DeepAgents stores raw provider API keys (Anthropic, OpenAI, etc.) in
|
||||
`~/.deepagents/.env`. Unlike OAuth tokens, these typically have no
|
||||
expiry and no per-request scope. Combined with the default Docker
|
||||
socket mount and any opt-in `--shell-allow-list all` Ralph override,
|
||||
a compromise of in-sandbox code can reach sibling containers, the
|
||||
host Docker daemon, AND your provider accounts.
|
||||
- **Memory / sessions bleed-through** — DeepAgents memory and session
|
||||
history from non-harness host work are visible inside the sandbox.
|
||||
- **Host writes** — the sandbox writes new memory, skills, and sessions
|
||||
directly to your host state. Sandbox-side cleanup propagates.
|
||||
- **Pre-existing `deepagents-auth` volume state is NOT migrated** — when
|
||||
you enable the overlay, anything previously stored in the sandbox's
|
||||
named volume is invisible; the host state is the only source of truth.
|
||||
|
||||
**Disabling the overlay (returning to the named volume):**
|
||||
|
||||
1. Remove `".devcontainer/docker-compose.deepagents-host.yml"` from
|
||||
`composeOverrides` in `.openharness/config.json`.
|
||||
2. Run `make sandbox` (or `make destroy && make sandbox`).
|
||||
|
||||
The base `deepagents-auth` named volume reattaches at
|
||||
`/home/sandbox/.deepagents`, and `entrypoint.sh` chowns it to UID 1000
|
||||
on next boot. Your host `~/.deepagents` directory is **not** touched
|
||||
by removing the overlay.
|
||||
|
||||
**Resetting the named volume without deleting host `~/.deepagents`:**
|
||||
|
||||
```bash
|
||||
docker compose -f .devcontainer/docker-compose.yml down
|
||||
docker volume rm "${SANDBOX_NAME:-openharness}_deepagents-auth"
|
||||
docker compose -f .devcontainer/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
This wipes only the sandbox-scoped named volume. Host
|
||||
`~/.deepagents` is left intact because the host-overlay bind-mount
|
||||
target and the named volume are independent storage.
|
||||
|
||||
**UID requirement — host UID must equal 1000 (the sandbox UID):**
|
||||
Provider-key files such as `~/.deepagents/.env` are typically mode
|
||||
`0600` (owner-only), so the group-membership reconciliation in
|
||||
`entrypoint.sh` does not grant access. The overlay sets
|
||||
`DEEPAGENTS_HOST_BIND_MOUNT=1` and the entrypoint skips its recursive
|
||||
`chown` to preserve host file ownership, but if your host UID is not
|
||||
1000, the sandbox user cannot read the provider env file and
|
||||
`deepagents` will fail to authenticate. `scripts/install.sh` strips
|
||||
this overlay from `composeOverrides` automatically when host UID is not
|
||||
1000 so the named-volume fallback takes over cleanly. Check with
|
||||
`id -u` on the host before enabling.
|
||||
@@ -27,9 +27,21 @@ AI coding agents normally prompt for permission before running commands or editi
|
||||
|-------|------|-------|
|
||||
| Claude Code | `--dangerously-skip-permissions` | `claude` (aliased in .bashrc) |
|
||||
| OpenAI Codex | `--dangerously-bypass-approvals-and-sandbox` | `codex` (aliased in .bashrc) |
|
||||
| DeepAgents | `-y --shell-allow-list recommended` (Ralph default) | none — call `deepagents` directly |
|
||||
|
||||
The env var `CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true` is also set in the base compose file so Claude Code operates without prompts even when invoked by heartbeats or the Slack bot.
|
||||
|
||||
### DeepAgents shell allow list
|
||||
|
||||
DeepAgents **disables shell execution in non-interactive (`-n`) mode unless a shell allow list is configured** via `-S`/`--shell-allow-list` or the `DEEPAGENTS_CLI_SHELL_ALLOW_LIST` env var. Open Harness defaults to `recommended` (a curated safe set), never `all`.
|
||||
|
||||
The Ralph harness (`scripts/ralph.sh --harness=deepagents`) exposes two overrides:
|
||||
|
||||
- `RALPH_DEEPAGENTS_FLAGS` — override the flag string (default `-y --shell-allow-list recommended -q --no-stream`). Operators can opt into `--shell-allow-list all` here when they accept unrestricted shell execution. **Do not include `--max-turns` in this string** — the cap is appended separately.
|
||||
- `RALPH_DEEPAGENTS_MAX_TURNS` — per-call turn cap (default `25`), always appended as `--max-turns "$RALPH_DEEPAGENTS_MAX_TURNS"` so a single DeepAgents call cannot hang the iteration.
|
||||
|
||||
> **`--shell-allow-list all` warning.** Combined with the mounted Docker socket (enabled by default in the base compose file), unrestricted shell execution can affect sibling containers or the host Docker daemon. Combined with the `deepagents-host` overlay, it can also reach raw host provider keys. Only use `all` for trusted tasks where you have accepted that risk.
|
||||
|
||||
## Docker socket access
|
||||
|
||||
The `docker` compose overlay mounts `/var/run/docker.sock` into the container, allowing agents to:
|
||||
@@ -44,7 +56,7 @@ This is enabled by default. If your agent doesn't need Docker access, remove the
|
||||
| Boundary | What it protects |
|
||||
|----------|-----------------|
|
||||
| Docker container | Host filesystem, host processes, host network (by default) |
|
||||
| Named volumes | Agent auth tokens (claude-auth, gh-config, cloudflared-auth) survive rebuilds but are container-scoped |
|
||||
| Named volumes | Agent auth tokens (claude-auth, codex-auth, pi-auth, deepagents-auth, gh-config, cloudflared-auth) survive rebuilds but are container-scoped |
|
||||
| Bind mount | workspace/ is shared between host and container — the only intentional bridge |
|
||||
| Network | Containers are on the default Docker bridge. Overlays like postgres create dedicated networks. |
|
||||
|
||||
|
||||
@@ -92,10 +92,11 @@ Debian Bookworm (slim). The `sandbox` user has passwordless sudo.
|
||||
|
||||
| Tool | Command | Description |
|
||||
|------|---------|-------------|
|
||||
| Claude Code | `claude` | Anthropic's coding agent (aliased to `claude --dangerously-skip-permissions`) |
|
||||
| Claude Code | `claude` | Anthropic's coding agent (aliased to `claude --dangerously-skip-permissions`) — default |
|
||||
| OpenAI Codex | `codex` | OpenAI's coding agent (aliased to `codex --dangerously-bypass-approvals-and-sandbox`) |
|
||||
| OpenCode | `opencode` | `opencode-ai` — terminal coding agent with OpenAI OAuth support |
|
||||
| Pi | `pi` | `@mariozechner/pi-coding-agent` — local-first coding agent |
|
||||
| DeepAgents | `deepagents` | LangChain's multi-provider terminal agent (`deepagents-cli` via `uv tool install`) — optional supported runtime |
|
||||
| agent-browser | `agent-browser` | Headless Chromium for web-capable agents |
|
||||
|
||||
The Mom Slack bot ships in the [`@ryaneggz/mifune`](https://github.com/ryaneggz/mifune) harness pack. Install by cloning the pack into your sandbox and following its README.
|
||||
@@ -148,5 +149,6 @@ Auth credentials survive container rebuilds via named Docker volumes:
|
||||
- `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](./guide/overlays.md#sharing-host-opencode-state-opencode-host), 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](./guide/overlays.md#sharing-host-deepagents-state-deepagents-host), 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)
|
||||
- `gh-config` → `~/.config/gh` (GitHub CLI tokens)
|
||||
+20
-6
@@ -31,18 +31,32 @@ Verify with `gh auth status`. If it succeeds, you are done with this step.
|
||||
|
||||
### Step 2 — LLM provider
|
||||
|
||||
Authenticate Claude Code, Codex, OpenCode, and Pi (each ships in the base sandbox) so they can make API calls.
|
||||
Authenticate Claude Code, Codex, OpenCode, Pi, and (optionally) DeepAgents — each ships in the base sandbox — so they can make API calls.
|
||||
|
||||
```bash
|
||||
claude # opens the OAuth flow on first run; follow the printed URL
|
||||
codex # OpenAI auth, only if you intend to use Codex
|
||||
opencode # run /connect, then choose OpenAI
|
||||
pi # @mariozechner/pi-coding-agent; OAuth on first run
|
||||
claude # opens the OAuth flow on first run; follow the printed URL
|
||||
codex # OpenAI auth, only if you intend to use Codex
|
||||
opencode # run /connect, then choose OpenAI
|
||||
pi # @mariozechner/pi-coding-agent; OAuth on first run
|
||||
deepagents # optional runtime; configure provider keys (see below)
|
||||
```
|
||||
|
||||
OpenCode's recommended default for ChatGPT Plus and Pro users is OpenAI OAuth through `/connect` → OpenAI. It writes auth to `~/.local/share/opencode/auth.json`.
|
||||
|
||||
Each agent writes tokens to its directory under the sandbox home (`~/.claude/`, `~/.codex/`, `~/.local/share/opencode/`, `~/.pi/`). With persistent named volumes, credentials survive container rebuilds. The `*-host` overlays (enabled by default in `.openharness/config.json`) bind-mount matching host directories so your laptop's existing auth carries straight in — see [provision.md](./operations/provision.md).
|
||||
Each agent writes tokens to its directory under the sandbox home (`~/.claude/`, `~/.codex/`, `~/.local/share/opencode/`, `~/.pi/`, `~/.deepagents/`). With persistent named volumes, credentials survive container rebuilds. The `*-host` overlays (Claude/Codex/OpenCode/Pi enabled by default in `.openharness/config.json`; DeepAgents opt-in) bind-mount matching host directories so your laptop's existing auth carries straight in — see [provision.md](./operations/provision.md).
|
||||
|
||||
DeepAgents is an optional supported runtime; Claude remains the default. DeepAgents reads provider keys from `~/.deepagents/.env`. Create it on first use:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.deepagents
|
||||
cat > ~/.deepagents/.env <<'EOF'
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
OPENAI_API_KEY=sk-...
|
||||
EOF
|
||||
chmod 600 ~/.deepagents/.env
|
||||
```
|
||||
|
||||
The sandbox banner reports `deepagents` as `installed` once `command -v deepagents` succeeds and as `configured` only when `~/.deepagents/.env` or `~/.deepagents/config.toml` is non-empty.
|
||||
|
||||
If you prefer API keys or provider env over OAuth, set the relevant variables in `.devcontainer/.env` (e.g. `OPENAI_API_KEY`) before bringing the sandbox up.
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ Tear down the sandbox. Replaces the deprecated `/destroy` skill — `docker comp
|
||||
docker compose -f .devcontainer/docker-compose.yml down -v
|
||||
```
|
||||
|
||||
Stops the container(s), removes the network, and **removes named volumes** declared in the base compose: `claude-auth`, `codex-auth`, `pi-auth`, `cloudflared-auth`, `gh-config`. Overlays add their own (e.g. `pgdata` from `docker-compose.postgres.yml`, `ssh-keys` from `docker-compose.ssh-generate.yml`) — pass those overlay files to `down -v` to clean them too. Drop the `-v` flag to preserve volumes for a faster next provision.
|
||||
Stops the container(s), removes the network, and **removes named volumes** declared in the base compose: `claude-auth`, `codex-auth`, `pi-auth`, `deepagents-auth`, `cloudflared-auth`, `gh-config`. Overlays add their own (e.g. `pgdata` from `docker-compose.postgres.yml`, `ssh-keys` from `docker-compose.ssh-generate.yml`) — pass those overlay files to `down -v` to clean them too. Drop the `-v` flag to preserve volumes for a faster next provision.
|
||||
|
||||
> To reset only the DeepAgents named volume without touching host `~/.deepagents` (when the `deepagents-host` overlay is enabled), see [Resetting the named volume without deleting host `~/.deepagents`](../guide/overlays.md#sharing-host-deepagents-state-deepagents-host).
|
||||
|
||||
## With overlays
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ Overlays live in `.devcontainer/docker-compose.*.yml` and are selected through `
|
||||
| `docker-compose.codex-host.yml` | Bind-mount host `~/.codex` (trust tradeoff) |
|
||||
| `docker-compose.opencode-host.yml` | Bind-mount host `~/.local/share/opencode` (trust tradeoff) |
|
||||
| `docker-compose.pi-host.yml` | Bind-mount host `~/.pi` (trust tradeoff) |
|
||||
| `docker-compose.deepagents-host.yml` | Bind-mount host `~/.deepagents` — opt-in, wider blast radius (raw provider keys) |
|
||||
| `docker-compose.ssh.yml` | Mount host `~/.ssh` read-only — git-over-SSH, mutually exclusive with `ssh-generate` |
|
||||
| `docker-compose.ssh-generate.yml` | Generate persistent ED25519 keypair in named volume |
|
||||
|
||||
> The `claude-host`, `codex-host`, `opencode-host`, and `pi-host` overlays all require host UID = 1000 and pre-existing host directories (`~/.claude` + `~/.claude.json` for claude; `~/.codex` for codex; `~/.local/share/opencode` for OpenCode; `~/.pi` for pi). See the trust tradeoffs in each `.devcontainer/docker-compose.*-host.yml` file.
|
||||
> The `claude-host`, `codex-host`, `opencode-host`, `pi-host`, and `deepagents-host` overlays all require host UID = 1000 and pre-existing host directories (`~/.claude` + `~/.claude.json` for claude; `~/.codex` for codex; `~/.local/share/opencode` for OpenCode; `~/.pi` for pi; `~/.deepagents` for deepagents). See the trust tradeoffs in each `.devcontainer/docker-compose.*-host.yml` file. `scripts/install.sh` pre-creates the host directories and strips `*-host.yml` overlays from `composeOverrides` automatically when host UID is not 1000.
|
||||
|
||||
To enable an overlay, add its path to `.openharness/config.json`:
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ If the container itself is down, `docker exec` returns non-zero. Bring it back w
|
||||
| `codex --version` fails | `docker exec -u sandbox openharness sudo npm install -g @openai/codex` |
|
||||
| `opencode --version` fails | `docker exec -u sandbox openharness sudo npm install -g opencode-ai` |
|
||||
| `pi --version` fails | `docker exec -u sandbox openharness sudo npm install -g @mariozechner/pi-coding-agent` |
|
||||
| `deepagents -v` fails | `docker exec -u sandbox openharness sudo env UV_TOOL_DIR=/opt/uv/tools UV_TOOL_BIN_DIR=/usr/local/bin uv tool install deepagents-cli` |
|
||||
| `deepagents` reports "not configured" | Create `~/.deepagents/.env` with provider keys (e.g. `ANTHROPIC_API_KEY=...`); `chmod 600`. The named `deepagents-auth` volume preserves it across rebuilds. |
|
||||
| Node version wrong | Image is stale. Rebuild: `docker compose ... down -v && docker compose ... up -d --build` |
|
||||
| `docker ps` fails inside sandbox | Verify the `/var/run/docker.sock` bind mount in `.devcontainer/docker-compose.yml` is intact; usually a host-side socket permission issue, not a missing overlay |
|
||||
| `tmux ls` missing `system-cron` | Restart entrypoint: `docker compose ... restart` (the cron runtime launches in tmux from `.devcontainer/entrypoint.sh`) |
|
||||
|
||||
+6
-1
@@ -37,12 +37,17 @@ The sandbox ships with several agent CLIs preinstalled. Launch whichever
|
||||
you prefer:
|
||||
|
||||
```bash
|
||||
claude # Claude Code
|
||||
claude # Claude Code (default)
|
||||
codex # OpenAI Codex CLI
|
||||
opencode # OpenCode
|
||||
pi # Pi Coding Agent
|
||||
deepagents # LangChain DeepAgents (multi-provider)
|
||||
```
|
||||
|
||||
DeepAgents is an optional supported runtime — Claude Code remains the
|
||||
documented default. See [the agents overview](./agents/overview) for the
|
||||
full list and per-agent setup.
|
||||
|
||||
If `GH_TOKEN` was set during install, the entrypoint already ran
|
||||
`gh auth login` and `gh auth setup-git` for you. Otherwise run them once
|
||||
inside the shell:
|
||||
|
||||
+23
-7
@@ -77,6 +77,21 @@ if [ -s "${HOME}/.local/share/opencode/auth.json" ]; then
|
||||
opencode_detail="authenticated"
|
||||
fi
|
||||
|
||||
# deepagents — installed status from PATH; configured status from
|
||||
# ~/.deepagents/.env or ~/.deepagents/config.toml so that an empty mounted
|
||||
# directory (named volume on first boot) is not treated as authenticated.
|
||||
deepagents_status="[✗]"
|
||||
deepagents_detail="not installed"
|
||||
if command -v deepagents >/dev/null 2>&1; then
|
||||
if [ -s "${HOME}/.deepagents/.env" ] || [ -s "${HOME}/.deepagents/config.toml" ]; then
|
||||
deepagents_status="[✓]"
|
||||
deepagents_detail="configured"
|
||||
else
|
||||
deepagents_status="[✓]"
|
||||
deepagents_detail="installed — configure ~/.deepagents/.env or run: deepagents"
|
||||
fi
|
||||
fi
|
||||
|
||||
# openharness CLI — verify the bind-mounted package built and symlinked
|
||||
oh_status="[✗]"
|
||||
oh_detail="not installed — check entrypoint logs"
|
||||
@@ -97,14 +112,15 @@ printf ' Timezone: %s\n' "$timezone"
|
||||
printf ' Overlays: %s\n' "$overlays"
|
||||
printf '\n'
|
||||
printf ' Onboarding:\n'
|
||||
printf ' %-6s %-11s %s\n' "$gh_status" "gh" "$gh_detail"
|
||||
printf ' %-6s %-11s %s\n' "$claude_status" "claude" "$claude_detail"
|
||||
printf ' %-6s %-11s %s\n' "$codex_status" "codex" "$codex_detail"
|
||||
printf ' %-6s %-11s %s\n' "$opencode_status" "opencode" "$opencode_detail"
|
||||
printf ' %-6s %-11s %s\n' "$pi_status" "pi" "$pi_detail"
|
||||
printf ' %-6s %-11s %s\n' "$oh_status" "openharness" "$oh_detail"
|
||||
printf ' %-6s %-11s %s\n' "$gh_status" "gh" "$gh_detail"
|
||||
printf ' %-6s %-11s %s\n' "$claude_status" "claude" "$claude_detail"
|
||||
printf ' %-6s %-11s %s\n' "$codex_status" "codex" "$codex_detail"
|
||||
printf ' %-6s %-11s %s\n' "$opencode_status" "opencode" "$opencode_detail"
|
||||
printf ' %-6s %-11s %s\n' "$pi_status" "pi" "$pi_detail"
|
||||
printf ' %-6s %-11s %s\n' "$deepagents_status" "deepagents" "$deepagents_detail"
|
||||
printf ' %-6s %-11s %s\n' "$oh_status" "openharness" "$oh_detail"
|
||||
printf '\n'
|
||||
printf ' Shortcuts: claude · codex · opencode · pi · tmux attach -t system-cron\n'
|
||||
printf ' Shortcuts: claude · codex · opencode · pi · deepagents · tmux attach -t system-cron\n'
|
||||
printf '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'
|
||||
printf '\n'
|
||||
|
||||
|
||||
+8
-4
@@ -295,8 +295,10 @@ fi
|
||||
|
||||
# ─── Pre-create host auth source dirs ────────────────────────────────
|
||||
# Default config enables host-bind overlays (claude-host.yml, codex-host.yml,
|
||||
# pi-host.yml, opencode-host.yml) that bind host agent state into the container. Two
|
||||
# preconditions (each documented in the overlay headers):
|
||||
# pi-host.yml, opencode-host.yml) that bind host agent state into the container.
|
||||
# The DeepAgents host overlay (deepagents-host.yml) is opt-in but we still
|
||||
# pre-create ~/.deepagents so the overlay works the moment a user enables it.
|
||||
# Two preconditions (each documented in the overlay headers):
|
||||
# 1. Host UID == 1000 (credential files are mode 0600 — group-membership
|
||||
# trick in entrypoint.sh cannot bypass owner-only reads).
|
||||
# 2. Host source dir pre-exists. Otherwise docker auto-creates it as
|
||||
@@ -306,7 +308,7 @@ fi
|
||||
# (1) is checked below and surfaced as a warning — the user must opt
|
||||
# out by hand if their host UID isn't 1000.
|
||||
banner "Preparing host auth dirs for sandbox bind-mounts"
|
||||
for d in .claude .codex .pi .local/share/opencode; do
|
||||
for d in .claude .codex .pi .deepagents .local/share/opencode; do
|
||||
if [ ! -d "$HOME/$d" ]; then
|
||||
mkdir -p "$HOME/$d"
|
||||
ok "Created ~/$d (empty — first-time auth will populate it)"
|
||||
@@ -355,11 +357,13 @@ if [ "$__HOST_UID" != "1000" ]; then
|
||||
warn "the sandbox WILL NOT be able to read them despite the bind-mount."
|
||||
warn ""
|
||||
warn "Install jq, OR edit $REPO_DIR/.openharness/config.json"
|
||||
warn "and remove these overlays from composeOverrides:"
|
||||
warn "and remove these overlays from composeOverrides (any that you"
|
||||
warn "have enabled — deepagents-host.yml is opt-in by default):"
|
||||
warn " .devcontainer/docker-compose.claude-host.yml"
|
||||
warn " .devcontainer/docker-compose.codex-host.yml"
|
||||
warn " .devcontainer/docker-compose.pi-host.yml"
|
||||
warn " .devcontainer/docker-compose.opencode-host.yml"
|
||||
warn " .devcontainer/docker-compose.deepagents-host.yml"
|
||||
warn ""
|
||||
warn "The base named volumes will take over"
|
||||
warn "and entrypoint.sh chowns them to UID 1000 on boot."
|
||||
|
||||
+67
-19
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/ralph.sh — Ralph loop runner per SPEC §scripts.
|
||||
#
|
||||
# Usage: scripts/ralph.sh [--harness=claude|pi|codex|opencode] <taskdesc>
|
||||
# Usage: scripts/ralph.sh [--harness=claude|pi|codex|opencode|deepagents] <taskdesc>
|
||||
# Example: scripts/ralph.sh openharness-v07-convergence
|
||||
# Example: scripts/ralph.sh --harness=pi openharness-v07-convergence
|
||||
# Example: scripts/ralph.sh --harness=codex openharness-v07-convergence
|
||||
# Example: scripts/ralph.sh --harness=opencode openharness-v07-convergence
|
||||
# Example: scripts/ralph.sh --harness=deepagents openharness-v07-convergence
|
||||
#
|
||||
# Validates the four-file contract (prd.md, prd.json, prompt.md, progress.txt),
|
||||
# launches a named tmux session running the loop. Each iteration sends prompt.md
|
||||
@@ -17,37 +18,56 @@
|
||||
# Idempotent: re-invoking on an existing session attaches instead of duplicating.
|
||||
#
|
||||
# Harnesses:
|
||||
# --harness=claude run Claude first (default); fall back to Pi after a
|
||||
# recognized Claude usage-limit message; Codex is fallback
|
||||
# after Pi if Pi is unavailable
|
||||
# --harness=pi run Pi for every iteration; fall back to Codex if Pi is
|
||||
# unavailable
|
||||
# --harness=codex run Codex for every iteration
|
||||
# --harness=opencode run OpenCode for every iteration
|
||||
# --harness=claude run Claude first (default); fall back to Pi after a
|
||||
# recognized Claude usage-limit message; Codex is fallback
|
||||
# after Pi if Pi is unavailable
|
||||
# --harness=pi run Pi for every iteration; fall back to Codex if Pi is
|
||||
# unavailable
|
||||
# --harness=codex run Codex for every iteration
|
||||
# --harness=opencode run OpenCode for every iteration
|
||||
# --harness=deepagents run DeepAgents for every iteration; explicit opt-in only,
|
||||
# no automatic fallback. Default flags use
|
||||
# --shell-allow-list recommended (constrained shell);
|
||||
# unrestricted shell requires an explicit operator
|
||||
# override via RALPH_DEEPAGENTS_FLAGS — combined with the
|
||||
# mounted Docker socket, --shell-allow-list all can affect
|
||||
# sibling containers or host Docker, so use only for
|
||||
# trusted tasks.
|
||||
#
|
||||
# Configuration (env vars):
|
||||
# RALPH_HARNESS default harness if --harness is omitted (claude|pi|codex|opencode)
|
||||
# RALPH_CLAUDE_FLAGS Claude flags (default: --dangerously-skip-permissions --print)
|
||||
# RALPH_AGENT_FLAGS backwards-compatible alias for RALPH_CLAUDE_FLAGS
|
||||
# RALPH_PI_FLAGS Pi flags (default: --print)
|
||||
# RALPH_OPENCODE_FLAGS OpenCode run flags (default: empty)
|
||||
# RALPH_MAX_ITERATIONS safety cap (default: 50)
|
||||
# RALPH_SLEEP seconds between iterations (default: 2)
|
||||
# RALPH_HARNESS default harness if --harness is omitted
|
||||
# (claude|pi|codex|opencode|deepagents)
|
||||
# RALPH_CLAUDE_FLAGS Claude flags (default: --dangerously-skip-permissions --print)
|
||||
# RALPH_AGENT_FLAGS backwards-compatible alias for RALPH_CLAUDE_FLAGS
|
||||
# RALPH_PI_FLAGS Pi flags (default: --print)
|
||||
# RALPH_OPENCODE_FLAGS OpenCode run flags (default: empty)
|
||||
# RALPH_DEEPAGENTS_FLAGS DeepAgents flags excluding the task and turn cap.
|
||||
# Default: "-y --shell-allow-list recommended -q --no-stream".
|
||||
# To allow unrestricted shell, set this to
|
||||
# "-y --shell-allow-list all -q --no-stream"; combined with
|
||||
# the mounted Docker socket, that can affect sibling
|
||||
# containers or host Docker — only use for trusted tasks.
|
||||
# RALPH_DEEPAGENTS_MAX_TURNS per-call turn cap passed to DeepAgents as --max-turns
|
||||
# (default: 25). Prevents a single DeepAgents call from
|
||||
# hanging an iteration; always appended after the
|
||||
# flag string above.
|
||||
# RALPH_MAX_ITERATIONS safety cap (default: 50)
|
||||
# RALPH_SLEEP seconds between iterations (default: 2)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [--harness=claude|pi|codex|opencode] <taskdesc>" >&2
|
||||
echo "Usage: $0 [--harness=claude|pi|codex|opencode|deepagents] <taskdesc>" >&2
|
||||
}
|
||||
|
||||
normalize_harness() {
|
||||
local harness="$1"
|
||||
case "$harness" in
|
||||
claude|pi|codex|opencode)
|
||||
claude|pi|codex|opencode|deepagents)
|
||||
printf '%s\n' "$harness"
|
||||
;;
|
||||
*)
|
||||
echo "Error: unknown harness '$harness' (expected: claude, pi, codex, or opencode)." >&2
|
||||
echo "Error: unknown harness '$harness' (expected: claude, pi, codex, opencode, or deepagents)." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -65,7 +85,7 @@ parse_args() {
|
||||
--harness)
|
||||
shift
|
||||
if [ "${1-}" = "" ]; then
|
||||
echo "Error: --harness requires a value (claude, pi, codex, or opencode)." >&2
|
||||
echo "Error: --harness requires a value (claude, pi, codex, opencode, or deepagents)." >&2
|
||||
exit 2
|
||||
fi
|
||||
HARNESS="$1"
|
||||
@@ -200,6 +220,27 @@ run_opencode() {
|
||||
opencode run $opencode_flags "$task"
|
||||
}
|
||||
|
||||
run_deepagents() {
|
||||
local task="$1"
|
||||
local max_turns="${RALPH_DEEPAGENTS_MAX_TURNS:-25}"
|
||||
# Default flags: non-interactive (-n is added below with the task), clean buffered
|
||||
# output (-q --no-stream), confirm-on-edits skipped (-y), and shell calls limited to
|
||||
# the recommended allow list. --shell-allow-list all opens unrestricted shell
|
||||
# execution; combined with the mounted Docker socket it can affect sibling
|
||||
# containers or the host Docker daemon, so it must be an explicit operator opt-in
|
||||
# via RALPH_DEEPAGENTS_FLAGS, never the default.
|
||||
local default_flags="-y --shell-allow-list recommended -q --no-stream"
|
||||
local deepagents_flags="${RALPH_DEEPAGENTS_FLAGS:-$default_flags}"
|
||||
|
||||
# --max-turns is always appended (even if the operator overrides
|
||||
# RALPH_DEEPAGENTS_FLAGS) so a single DeepAgents call cannot hang the iteration.
|
||||
# Operators who set RALPH_DEEPAGENTS_FLAGS should not include --max-turns there;
|
||||
# adjust the cap via RALPH_DEEPAGENTS_MAX_TURNS instead.
|
||||
# Intentionally leave flags unquoted so callers can provide multiple flags.
|
||||
# shellcheck disable=SC2086
|
||||
deepagents $deepagents_flags --max-turns "$max_turns" -n "$task"
|
||||
}
|
||||
|
||||
run_iteration() {
|
||||
local harness="$1"
|
||||
local task="$2"
|
||||
@@ -224,6 +265,10 @@ run_iteration() {
|
||||
run_opencode "$task" 2>&1 | tee "$output_file"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
deepagents)
|
||||
run_deepagents "$task" 2>&1 | tee "$output_file"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
*)
|
||||
echo "Error: internal unknown harness '$harness'." >&2
|
||||
status=2
|
||||
@@ -245,6 +290,8 @@ if [ "${1-}" = "--loop" ]; then
|
||||
PROGRESS="$TASK_DIR/progress.txt"
|
||||
ACTIVE_HARNESS="$(resolve_initial_harness "$HARNESS")"
|
||||
CLAUDE_FLAGS="${RALPH_CLAUDE_FLAGS:-${RALPH_AGENT_FLAGS:---dangerously-skip-permissions --print}}"
|
||||
DEEPAGENTS_FLAGS="${RALPH_DEEPAGENTS_FLAGS:--y --shell-allow-list recommended -q --no-stream}"
|
||||
DEEPAGENTS_MAX_TURNS="${RALPH_DEEPAGENTS_MAX_TURNS:-25}"
|
||||
MAX_ITER="${RALPH_MAX_ITERATIONS:-50}"
|
||||
SLEEP_SECONDS="${RALPH_SLEEP:-2}"
|
||||
|
||||
@@ -257,6 +304,7 @@ if [ "${1-}" = "--loop" ]; then
|
||||
printf '│ pi: pi %s <task>\n' "${RALPH_PI_FLAGS:---print}"
|
||||
printf '│ codex: codex exec --sandbox danger-full-access <task>\n'
|
||||
printf '│ opencode: opencode run %s <task>\n' "${RALPH_OPENCODE_FLAGS:-}"
|
||||
printf '│ deepagents: deepagents %s --max-turns %s -n <task>\n' "$DEEPAGENTS_FLAGS" "$DEEPAGENTS_MAX_TURNS"
|
||||
printf '│ max iterations: %s\n' "$MAX_ITER"
|
||||
printf '│ progress: %s\n╰─\n\n' "$PROGRESS"
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 1 on 2026-05-03; commit 180e07f"
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
@@ -37,8 +37,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 2 on 2026-05-03; commit 1cfea26"
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
@@ -56,8 +56,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 3 on 2026-05-03; commit 8f81e24"
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
@@ -77,8 +77,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 4 on 2026-05-03; commit 1fb1e37"
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
@@ -97,8 +97,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 5 on 2026-05-03; commit 51ab9c5"
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
@@ -112,8 +112,8 @@
|
||||
"Typecheck passes."
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed iteration 6 on 2026-05-03; commit 22641c4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +1,308 @@
|
||||
# progress
|
||||
|
||||
## Codebase Patterns
|
||||
|
||||
- The Dockerfile installs `uv` to `/usr/local/bin` already (line 47-48). Adding
|
||||
Python tools via `uv tool install` should set `UV_TOOL_DIR=/opt/uv/tools`
|
||||
and `UV_TOOL_BIN_DIR=/usr/local/bin` so the resulting executable lands on
|
||||
the system PATH and is usable by the unprivileged `sandbox` user. Avoid
|
||||
installing into `/root/.local/bin` or `/home/sandbox/.local/bin`.
|
||||
- npm-based agent CLIs are installed via the `AGENTS` build arg loop near the
|
||||
bottom of the Dockerfile (claude-code, codex, pi-coding-agent). DeepAgents
|
||||
is a Python package via `uv tool install` so it lives in its own layer next
|
||||
to the `uv` install, NOT in the AGENTS arg array.
|
||||
- Project root has root `package.json` with `lint` (delegates to package-level
|
||||
lints if present) and `test` (vitest). No per-package lint configured yet;
|
||||
`pnpm run lint` is a no-op until packages add their own.
|
||||
- Pre-commit hook runs `pnpm run lint && pnpm run test` automatically — do
|
||||
NOT use `--no-verify`. Tests in `scripts/__tests__/cron-runtime.test.ts`
|
||||
must keep passing.
|
||||
- The orchestrator-protected paths in `.claude/protected-paths.txt` include
|
||||
`.devcontainer/Dockerfile`, `.devcontainer/entrypoint.sh`, `install/banner.sh`,
|
||||
and `scripts/ralph.sh` — modify additively, do not delete.
|
||||
- DeepAgents Ralph env-var contract (set by US-004): `RALPH_DEEPAGENTS_FLAGS`
|
||||
controls the flag string before the task; the per-call turn cap is set
|
||||
exclusively via `RALPH_DEEPAGENTS_MAX_TURNS` and is always appended
|
||||
AFTER the flag string. Operators must NOT include `--max-turns` in
|
||||
`RALPH_DEEPAGENTS_FLAGS`. Default flags:
|
||||
`-y --shell-allow-list recommended -q --no-stream`. Default max turns: 25.
|
||||
US-005 docs and US-006 changelog must restate this contract.
|
||||
|
||||
---
|
||||
|
||||
## US-001 — 2026-05-03 19:51 UTC
|
||||
|
||||
**Title**: Install DeepAgents in the sandbox image
|
||||
**Files changed**: .devcontainer/Dockerfile
|
||||
**Commit**: 180e07f
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Added a dedicated DeepAgents install layer in `.devcontainer/Dockerfile`
|
||||
immediately after the `uv` install. The layer sets
|
||||
`UV_TOOL_DIR=/opt/uv/tools` and `UV_TOOL_BIN_DIR=/usr/local/bin` as ENV (so
|
||||
they're available to the rest of the image, not just the RUN), creates the
|
||||
tool dir, and runs `uv tool install deepagents-cli`. This puts the
|
||||
`deepagents` shim on `/usr/local/bin`, which is on PATH for the sandbox
|
||||
user. The npm-based agent CLI block (claude, codex, pi) below is
|
||||
unchanged.
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- `uv tool install` writes a shim to `$UV_TOOL_BIN_DIR`. Setting that to
|
||||
`/usr/local/bin` is the cleanest way to surface a Python CLI to all
|
||||
users in the image — no PATH rewriting needed.
|
||||
- Future iterations that touch the Dockerfile should keep the layer ordering
|
||||
stable: system pkgs → Node → gh → Docker CLI → cloudflared → bun → uv →
|
||||
DeepAgents → pnpm → npm-based agents → sandbox user → oh-my-zsh → COPY →
|
||||
entrypoint. Adding new tools too late in the file means they can't be
|
||||
invoked during user-config steps; too early misses dependencies.
|
||||
- The pre-commit hook re-runs the full lint+test suite on commit. Keep
|
||||
iterations small so the hook stays fast.
|
||||
- US-002 will need a named volume entry in `.devcontainer/docker-compose.yml`
|
||||
and an `entrypoint.sh` ownership pass tweak. The entrypoint already loops
|
||||
over a list of mounted dirs — extending that list is the right pattern.
|
||||
|
||||
---
|
||||
|
||||
## US-002 — 2026-05-03 19:54 UTC
|
||||
|
||||
**Title**: Persist DeepAgents state
|
||||
**Files changed**: .devcontainer/docker-compose.yml, .devcontainer/entrypoint.sh
|
||||
**Commit**: 1cfea26
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Added `deepagents-auth` named volume to `.devcontainer/docker-compose.yml`,
|
||||
mounted at `/home/sandbox/.deepagents`, with a comment block stating that
|
||||
v1 persists ONLY this path (repo-local `.deepagents/` is project data
|
||||
and follows normal git review/ignore rules — no secrets there). Extended
|
||||
the entrypoint ownership-pass loop to include `.deepagents`, with a skip
|
||||
clause for `DEEPAGENTS_HOST_BIND_MOUNT=1` matching the existing
|
||||
Claude/Codex/Pi pattern. Existing `claude-auth`, `codex-auth`, `pi-auth`,
|
||||
`cloudflared-auth`, and `gh-config` volumes are unchanged; OpenCode has
|
||||
no auth volume in the current compose file (nothing to preserve).
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- The `for dir in ...` ownership loop in `entrypoint.sh` is the canonical
|
||||
pattern for new mounted dirs. Add a `[ "$dir" = ".X" ] && [ "${X_HOST_BIND_MOUNT:-0}" = "1" ]`
|
||||
skip clause when adding a host-overlay companion. US-003 will create
|
||||
`docker-compose.deepagents-host.yml` that flips this env var.
|
||||
- The compose file's top-of-file comment block is the right place to
|
||||
document each volume's purpose and persistence semantics; it surfaces
|
||||
during code review without needing a separate doc page.
|
||||
- `docker compose ... config --quiet` is a fast validity check for
|
||||
compose changes without booting the container.
|
||||
- The persistence story splits cleanly: `~/.deepagents` (named volume,
|
||||
rebuild-stable) vs. repo-local `.deepagents/` (project data, gitignore
|
||||
rules apply). US-005 docs should restate this separation.
|
||||
|
||||
---
|
||||
|
||||
## US-003 — 2026-05-03 20:00 UTC
|
||||
|
||||
**Title**: Add a DeepAgents host-state overlay
|
||||
**Files changed**: .devcontainer/docker-compose.deepagents-host.yml, scripts/install.sh, docs/guide/overlays.md
|
||||
**Commit**: 8f81e24
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Added `.devcontainer/docker-compose.deepagents-host.yml` modeled on the
|
||||
existing pi-host overlay. It bind-mounts `${HOST_DEEPAGENTS_DIR:-~/.deepagents}`
|
||||
into `/home/sandbox/.deepagents` and sets `DEEPAGENTS_HOST_BIND_MOUNT=1` so the
|
||||
entrypoint chown loop (already extended in US-002) leaves host file ownership
|
||||
intact. Header comments call out the wider blast radius vs. claude/codex —
|
||||
DeepAgents stores raw provider API keys (no expiry, no scope) — and the
|
||||
specific `--shell-allow-list all` + Docker socket combo risk that US-004
|
||||
will surface in `scripts/ralph.sh`.
|
||||
|
||||
Updated `scripts/install.sh` to (1) include `.deepagents` in the host-auth
|
||||
pre-create loop alongside `.claude/.codex/.pi`, and (2) extend the UID-guard
|
||||
fallback warning with the deepagents-host overlay path and the
|
||||
`deepagents-auth` named volume, so non-UID-1000 hosts get a coherent
|
||||
fallback message. The existing `jq` filter already strips any `*-host.yml`
|
||||
overlay automatically, so behavior change is limited to comments + warnings.
|
||||
|
||||
Documented the overlay in `docs/guide/overlays.md`: enabling, the host-state
|
||||
pre-flight, credential blast-radius tradeoffs, disable-and-return-to-named-volume
|
||||
flow, and how to reset the `deepagents-auth` volume without touching host
|
||||
`~/.deepagents`. Did not enable the overlay by default — DeepAgents itself is
|
||||
an optional supported runtime (Claude is the documented default), so opt-in
|
||||
matches the PRD framing.
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- `scripts/install.sh` already has a generic `*-host.yml` jq filter for the
|
||||
UID guard, so adding new host overlays is mostly a documentation/warning
|
||||
exercise, not a code-flow change. Only the user-facing fallback warning
|
||||
needs the new overlay path so the manual instructions match the auto path.
|
||||
- The deepagents-host overlay's blast radius is genuinely larger than
|
||||
claude-host/codex-host because raw provider API keys have no scope/expiry.
|
||||
Future docs (US-005) and the Ralph harness story (US-004) should restate
|
||||
this when discussing `--shell-allow-list all` + the Docker socket.
|
||||
- `.openharness/config.json` keeps DeepAgents host overlay opt-out by
|
||||
default; do NOT add it to `composeOverrides` in a future iteration unless
|
||||
the PRD changes.
|
||||
- `docker compose ... config --quiet` with both base + overlay is the fast
|
||||
smoke test for new overlay files; it caught a YAML mistake in seconds
|
||||
during this iteration.
|
||||
- The pi-host overlay header is the cleanest single-mount template to copy
|
||||
for future single-dir agent overlays — claude-host's two-mount layout is
|
||||
the special case (because of the sibling `~/.claude.json`).
|
||||
|
||||
---
|
||||
|
||||
## US-004 — 2026-05-04 02:05 UTC
|
||||
|
||||
**Title**: Add DeepAgents to Ralph harness selection
|
||||
**Files changed**: scripts/ralph.sh
|
||||
**Commit**: 1fb1e37
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Added `deepagents` to `normalize_harness`, `usage`, top-of-file usage docs,
|
||||
and the iteration banner. Implemented `run_deepagents` with a default
|
||||
flag set of `-y --shell-allow-list recommended -q --no-stream` and
|
||||
appended `--max-turns "$RALPH_DEEPAGENTS_MAX_TURNS"` (default 25) plus
|
||||
`-n "$task"` so a single DeepAgents call cannot hang the iteration.
|
||||
`RALPH_DEEPAGENTS_FLAGS` overrides the safety/output flag string but the
|
||||
turn cap is always appended. Inline comments restate that
|
||||
`--shell-allow-list all` combined with the mounted Docker socket can
|
||||
affect sibling containers or host Docker. Existing Claude/Pi/Codex
|
||||
fallback paths and `claude_limit_detected` are untouched. Smoke-tested
|
||||
`--harness=deepagents`, `RALPH_HARNESS=deepagents`, unknown-harness
|
||||
rejection, and the `command -v deepagents` requirement.
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- `run_<harness>` functions in `scripts/ralph.sh` follow a stable shape:
|
||||
pull flags from an env-var override, document the unquoted-flags
|
||||
shellcheck disable, pass the task last. Match this exactly for any
|
||||
future harness — the `run_iteration` switch just dispatches to the
|
||||
matching `run_<harness>`.
|
||||
- The protected-paths reminder in AC #8 is satisfied by the Critical
|
||||
rules block in `tasks/deepagents-cli-support/prompt.md` ("Do not
|
||||
delete protected paths…"), which already restates the constraint.
|
||||
No prompt-generation skill produces a separate prompt.
|
||||
- `--max-turns` is intentionally appended after `$deepagents_flags` so
|
||||
the cap is never silently dropped by an operator override.
|
||||
RALPH_DEEPAGENTS_FLAGS should NOT include `--max-turns`; if it does,
|
||||
DeepAgents sees two values and the result is undefined. Document this
|
||||
explicitly in US-005.
|
||||
- `resolve_initial_harness` only auto-falls-back from `pi`. New
|
||||
harnesses default to "hard fail when not on PATH", which is the
|
||||
correct posture for explicit opt-in selection — DeepAgents inherits
|
||||
this for free without code changes.
|
||||
- The pre-commit hook re-runs lint+test on every commit; tests in
|
||||
`scripts/__tests__/cron-runtime.test.ts` are the only coverage today,
|
||||
so harness changes need their own bash-level smoke tests during
|
||||
iteration before the hook runs.
|
||||
|
||||
---
|
||||
|
||||
## US-005 — 2026-05-04 02:13 UTC
|
||||
|
||||
**Title**: Document DeepAgents support
|
||||
**Files changed**: install/banner.sh, docs/agents/deepagents.md (new),
|
||||
docs/agents/overview.md, docs/quickstart.md, docs/onboarding.md,
|
||||
docs/installation.md, docs/operations/{provision,destroy,repair}.md,
|
||||
docs/guide/{permissions,configuration}.md,
|
||||
docs/architecture/{container-runtime,overview}.md, README.md
|
||||
**Commit**: 51ab9c5
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Added DeepAgents to the user-facing docs surface alongside Claude/Codex/
|
||||
OpenCode/Pi without dropping OpenCode from the supported set. Created
|
||||
`docs/agents/deepagents.md` with purpose, install verification,
|
||||
provider-key setup (`~/.deepagents/.env`), interactive + non-interactive
|
||||
usage, tmux usage, Ralph usage, the `RALPH_DEEPAGENTS_FLAGS` /
|
||||
`RALPH_DEEPAGENTS_MAX_TURNS` env contract, and an explicit
|
||||
`--shell-allow-list all` warning that calls out the Docker socket +
|
||||
`deepagents-host` overlay blast radius. Updated the overview/installation/
|
||||
operations/guide/architecture docs to enumerate the
|
||||
`deepagents-auth` named volume, the `deepagents-host` overlay, the Ralph
|
||||
selection flag, and the repo-local `.deepagents/` "project data, never
|
||||
secrets" boundary. Reframed the multi-agent posture as "preinstalled
|
||||
alternatives, pick the right tool" rather than racing/stacking, per
|
||||
Critic B's v1 ICP guidance.
|
||||
|
||||
Extended `install/banner.sh` so DeepAgents status is `installed` only
|
||||
when `command -v deepagents` succeeds and `configured` only when
|
||||
`~/.deepagents/.env` or `~/.deepagents/config.toml` is non-empty — an
|
||||
empty mounted directory (named volume on first boot) is no longer
|
||||
treated as authenticated. Verified `pnpm docs:build`, `pnpm run lint`,
|
||||
`pnpm run test`, and `bash -n install/banner.sh` all pass.
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- `apps/docs` Docusaurus uses `onBrokenLinks: "warn"` and
|
||||
`onBrokenMarkdownLinks: "warn"`, so a typo in a relative link will
|
||||
silently warn instead of failing the build. Watch the build output
|
||||
for `[WARNING] Broken link…` lines when adding inter-doc anchors.
|
||||
- The overlays page already had a complete `deepagents-host` section
|
||||
from US-003 (lines 145–242); US-005 only needed to thread it into the
|
||||
rest of the doc set. Future doc passes should grep for an existing
|
||||
section before duplicating content.
|
||||
- `docs/agents/overview.md` previously listed only Claude/Codex/Pi and
|
||||
omitted OpenCode entirely; that was the stale-inventory finding from
|
||||
the critique. Always reconcile preinstalled-agent tables against the
|
||||
Dockerfile when adding a new agent — drift between the two is the
|
||||
most common doc bug for this feature class.
|
||||
- The banner status pattern is `[installed]` from `command -v` and
|
||||
`[configured]` from a non-empty config file — never from directory
|
||||
existence alone. The `deepagents-auth` named volume creates an empty
|
||||
`~/.deepagents/` on first boot, so an existence check would always be
|
||||
green. US-006 changelog should not need to restate this.
|
||||
- US-006 is the only remaining story; it owns the CHANGELOG entry per
|
||||
the iteration spec (no earlier story added one).
|
||||
|
||||
---
|
||||
|
||||
## US-006 — 2026-05-03 20:16 UTC
|
||||
|
||||
**Title**: Add release note
|
||||
**Files changed**: CHANGELOG.md
|
||||
**Commit**: 22641c4
|
||||
**Result**: PASS
|
||||
|
||||
### What I did
|
||||
|
||||
Appended a single comprehensive bullet to the first existing `### Added`
|
||||
section under `## [Unreleased]` (no duplicate heading created, per AC).
|
||||
The entry covers the Dockerfile install layer (US-001), the
|
||||
`deepagents-auth` named volume + entrypoint chown skip (US-002), the
|
||||
opt-in `docker-compose.deepagents-host.yml` overlay and its wider
|
||||
blast radius (US-003), the Ralph harness selector with the
|
||||
`-y --shell-allow-list recommended -q --no-stream` default,
|
||||
`RALPH_DEEPAGENTS_FLAGS` override, and `RALPH_DEEPAGENTS_MAX_TURNS`
|
||||
cap (US-004), and the docs/onboarding updates plus the corrected
|
||||
`install/banner.sh` `[installed]`/`[configured]` semantics (US-005).
|
||||
Linked issue #237. Verified `pnpm run lint`, `pnpm run test`, and
|
||||
`pnpm docs:build` all pass; pre-commit hook re-ran lint+test on commit.
|
||||
|
||||
### Learnings for future iterations
|
||||
|
||||
- The `[Unreleased]` block in `CHANGELOG.md` is unusual: it has
|
||||
multiple `### Added` subsections (lines 11, 23, 46) interleaved with
|
||||
`### Changed`/`### Removed` blocks rather than a single normalized
|
||||
set. The iteration spec rule "append to existing `### Added`"
|
||||
refers to the FIRST one — putting the new bullet at the top of the
|
||||
first `### Added` section (line 11) keeps recent entries together
|
||||
and matches how prior PRs (#233, etc.) seeded that section.
|
||||
- One-shot release-note iterations are still subject to the
|
||||
pre-commit hook (lint + test), so even a doc-only commit re-runs
|
||||
the vitest suite. Budget ~5s per commit.
|
||||
- Final iteration must terminate with a literal `STATUS: COMPLETE`
|
||||
line on its own at the very end of `progress.txt` — that's the
|
||||
Ralph loop's exit sentinel.
|
||||
|
||||
---
|
||||
|
||||
STATUS: COMPLETE
|
||||
Reference in new issue
Block a user