Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2720cd5
fix(evals): record the source lesson on five headerless probes (#892)
ryaneggz Aug 29, 2026
ad4d035
fix(delegate): track the wave graph in the .oh/tasks ledger (#888)
ryaneggz Aug 29, 2026
9bc367e
FROM task/898-single-home-mount TO development (#899)
ryaneggz Aug 31, 2026
bee8060
FROM task/900-slim-sandbox-image TO task/898-single-home-mount (#901)
ryaneggz Aug 31, 2026
09dfa8c
FROM task/902-provision-harnesses TO development (#903)
ryaneggz Aug 31, 2026
0b6dad5
FROM task/904-unbake-harnesses TO development (#905)
ryaneggz Aug 31, 2026
744e5c3
FROM task/906-tools-out-of-image TO development (#907)
ryaneggz Aug 31, 2026
ecfda4c
FROM task/908-cli-first-class TO development (#909)
ryaneggz Aug 31, 2026
3a00bb0
FROM task/910-retire-deepagents TO development (#911)
ryaneggz Aug 31, 2026
a679a04
feat(cli): support T3 Code mobile access over Tailscale (#897)
ryaneggz Aug 31, 2026
159ccac
skill: add a slop gate to /audit implementation (#913)
ryaneggz Aug 31, 2026
cc6bd6e
skill: make /spec ship a PR by default when handed a plan (#917)
ryaneggz Aug 31, 2026
933a59f
skill: close the lesson→skill loop with a wiki pattern layer and skil…
ryaneggz Aug 31, 2026
1fdeee5
skill: add /escalate so an unattended session can reach a human (#921)
ryaneggz Aug 31, 2026
db6fe74
task: give .oh/logs an AGENTS.md guide instead of a README (#925)
ryaneggz Aug 31, 2026
1c5f372
FROM task/920-compose-env-boundary TO development (#922)
ryaneggz Sep 1, 2026
ecc4980
FROM task/918-retire-prime-agent TO development (#923)
ryaneggz Sep 1, 2026
0f87d98
FROM task/928-retire-spec-agent-handoff TO development (#930)
ryaneggz Sep 1, 2026
cdc34ef
feat: make skills the role primitive and add /architect (#931)
ryaneggz Sep 1, 2026
fb681d1
FROM task/933-retire-cron-worktree-isolation TO development (#934)
ryaneggz Sep 1, 2026
11f21a2
FROM feat/926-repo-knowledge-loop TO development (#927)
ryaneggz Sep 1, 2026
e647bc8
release: cut 0.6.0
ryaneggz Sep 1, 2026
4fbca95
release: carry 0.6.0 into the CLI package and its lockfile
ryaneggz Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .claude/agents

This file was deleted.

3 changes: 3 additions & 0 deletions .claude/protected-paths.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ cloudflared
agent-browser
prd
ralph
architect
audit
delegate
strategic-proposal
spec
eval
health-check
retro
t3
# --- Orchestrator scripts (.oh/scripts/) ---
.oh/skills/wiki/scripts/knowledge-impact.sh
.oh/scripts/cron-runtime.ts
.oh/scripts/sandbox-healthcheck.sh
.oh/scripts/link-providers.sh
Expand Down
1 change: 0 additions & 1 deletion .codex/agents

This file was deleted.

112 changes: 26 additions & 86 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
FROM node:22-trixie-slim
FROM node:22-trixie-slim AS base

ARG OH_PROJECT_ROOT=/home/sandbox/harness
ENV OH_PROJECT_ROOT=${OH_PROJECT_ROOT}
ENV OH_PROJECT_ROOT=/home/sandbox/harness

ENV TZ=America/Denver

Expand Down Expand Up @@ -29,87 +28,21 @@
&& apt-get update && apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/*

# Cloudflare publishes no trixie suite (pkg.cloudflare.com/cloudflared/dists/trixie returns HTTP 404);
# the bookworm package is compatible with trixie, so this suite stays pinned to bookworm.
RUN curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
-o /usr/share/keyrings/cloudflare-main.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main" \
> /etc/apt/sources.list.d/cloudflared.list \
&& apt-get update && apt-get install -y --no-install-recommends cloudflared \
&& rm -rf /var/lib/apt/lists/*

RUN BUN_INSTALL=/usr/local curl -fsSL https://bun.sh/install | bash

ARG HERDR_VERSION=0.7.4
RUN case "$(dpkg --print-architecture)" in \
amd64) herdr_arch=x86_64; herdr_sha=bc0fc02d4ba500f9cac2353a43e67fe036785ecca6eb55378e050fac3c103059 ;; \
arm64) herdr_arch=aarch64; herdr_sha=544e0002de42806d1ab64ccdef3a7e7414f24717b0b6b022bc9e57d2eefd26a2 ;; \
*) echo "Unsupported Herdr architecture: $(dpkg --print-architecture)" >&2; exit 1 ;; \
esac \
&& curl -fsSL "https://github.com/ogulcancelik/herdr/releases/download/v${HERDR_VERSION}/herdr-linux-${herdr_arch}" \
-o /usr/local/bin/herdr \
&& echo "${herdr_sha} /usr/local/bin/herdr" | sha256sum -c - \
&& chmod 0755 /usr/local/bin/herdr \
&& test "$(herdr --version)" = "herdr ${HERDR_VERSION}"

RUN curl -LsSf https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1 sh \
&& cp /root/.local/bin/uv /usr/local/bin/uv \
&& cp /root/.local/bin/uvx /usr/local/bin/uvx

ARG INSTALL_HERMES=false
RUN if [ "${INSTALL_HERMES}" = "true" ]; then \
curl -fsSL https://hermes-agent.nousresearch.com/install.sh \
| bash -s -- --skip-setup --skip-browser; \
rm -rf /root/.hermes; \
uv pip install --python /usr/local/lib/hermes-agent/venv/bin/python 'hermes-agent[slack,teams,web,pty]'; \
else \
echo "Skipping Hermes Agent CLI install (INSTALL_HERMES=false)"; \
fi

ARG INSTALL_DEEPAGENTS=false
RUN export UV_TOOL_DIR=/opt/uv/tools UV_TOOL_BIN_DIR=/usr/local/bin \
&& mkdir -p "$UV_TOOL_DIR" \
&& if [ "${INSTALL_DEEPAGENTS}" = "true" ]; then \
uv tool install deepagents-cli; \
else \
echo "Skipping DeepAgents CLI install (INSTALL_DEEPAGENTS=false)"; \
fi

ENV PNPM_HOME="/usr/local/share/pnpm"
ENV NPM_USER_PREFIX="/home/sandbox/.local"
ENV PATH="$NPM_USER_PREFIX/bin:$PNPM_HOME:$PATH"
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate \

Check failure on line 41 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

SC2015 info: Note that A && B || C is not if-then-else. C may run when A is true.

Check failure on line 41 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

SC2015 info: Note that A && B || C is not if-then-else. C may run when A is true.
&& pnpm setup --force 2>/dev/null || true

SHELL ["/bin/bash", "-c"]

ARG AGENTS="claude-code,codex,pi-coding-agent"
ARG INSTALL_OPENCODE=false
ARG INSTALL_GROK_BUILD=false

RUN set -e; \
declare -A PKG=( \
[claude-code]=@anthropic-ai/claude-code \
[codex]=@openai/codex \
[pi-coding-agent]=@earendil-works/pi-coding-agent \
[opencode]=opencode-ai \
); \
IFS=',' read -ra agents <<< "$AGENTS"; \
for a in "${agents[@]}"; do \
if [ "$a" = "pi-coding-agent" ]; then continue; fi; \
pkg="${PKG[$a]:-}"; \
if [ -n "$pkg" ]; then npm install -g "$pkg"; \
else echo "Unknown agent: $a"; exit 1; fi; \
done; \
if [ "${INSTALL_OPENCODE}" = "true" ]; then npm install -g opencode-ai; \
else echo "Skipping OpenCode CLI install (INSTALL_OPENCODE=false)"; fi; \
if [ "${INSTALL_GROK_BUILD}" = "true" ]; then \
curl -fsSL https://x.ai/cli/install.sh | HOME=/opt/grok-build GROK_BIN_DIR=/opt/grok-build/bin bash -s 0.2.39; \
ln -sf /opt/grok-build/bin/grok /usr/local/bin/grok; \
rm -f /usr/local/bin/agent; \
else echo "Skipping Grok Build CLI install (INSTALL_GROK_BUILD=false)"; fi

RUN npm install -g cc-safety-net@1.0.6

COPY .oh/cli/ /opt/oh/
Expand All @@ -127,20 +60,20 @@
ENV UV_TOOL_BIN_DIR=/home/sandbox/.local/bin
ENV UV_PYTHON_INSTALL_DIR=/home/sandbox/.local/share/uv/python
ENV UV_CACHE_DIR=/home/sandbox/.cache/uv
RUN install -d -o sandbox -g sandbox \

FROM base AS home
RUN install -d -o sandbox -g sandbox -m 0700 /home/sandbox/.ssh \
&& install -d -o sandbox -g sandbox \
/home/sandbox/.local \
/home/sandbox/.local/share \
/home/sandbox/.local/share/uv \
/home/sandbox/.cache \
"$UV_TOOL_DIR" "$UV_TOOL_BIN_DIR" "$UV_PYTHON_INSTALL_DIR" "$UV_CACHE_DIR" \
&& chown -R sandbox:sandbox /opt/uv 2>/dev/null || true \
&& if [ -d /usr/local/lib/hermes-agent ]; then chown -R sandbox:sandbox /usr/local/lib/hermes-agent; fi
/home/sandbox/.config \
/home/sandbox/.herdr \
"$UV_TOOL_DIR" "$UV_TOOL_BIN_DIR" "$UV_PYTHON_INSTALL_DIR" "$UV_CACHE_DIR"

RUN set -e; \
install -d -o sandbox -g sandbox "$NPM_USER_PREFIX"; \
if [[ ",${AGENTS}," == *",pi-coding-agent,"* ]]; then \
su - sandbox -c 'npm --prefix "$HOME/.local" install -g --ignore-scripts @earendil-works/pi-coding-agent'; \
fi
RUN install -d -o sandbox -g sandbox "$NPM_USER_PREFIX" \
&& rm -rf /home/sandbox/.npm

RUN su - sandbox -c "RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\" --unattended" \
&& su - sandbox -c "git clone --depth 1 https://github.com/zsh-users/zsh-autosuggestions /home/sandbox/.oh-my-zsh/custom/plugins/zsh-autosuggestions" \
Expand All @@ -151,11 +84,10 @@

COPY --chown=sandbox:sandbox .oh/install/.zshrc /home/sandbox/.zshrc

RUN printf '%s\n' \

Check failure on line 87 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

SC2016 info: Expressions don't expand in single quotes, use double quotes for that.

Check failure on line 87 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

SC2016 info: Expressions don't expand in single quotes, use double quotes for that.
'export NPM_USER_PREFIX="/home/sandbox/.local"' \
'export PNPM_HOME="/usr/local/share/pnpm"' \
'export PATH="$NPM_USER_PREFIX/bin:$PNPM_HOME:$PATH"' \
'# uv/Python env written by .oh/scripts/provision-python.sh (PRIME_AGENT_KERNEL_PYTHON).' \
'[ -r "$HOME/.local/share/oh/python-env.sh" ] && . "$HOME/.local/share/oh/python-env.sh"' \
| tee -a /home/sandbox/.profile /home/sandbox/.zprofile \
&& chown sandbox:sandbox /home/sandbox/.profile /home/sandbox/.zprofile
Expand All @@ -165,17 +97,11 @@

RUN su - sandbox -c "git config --global --add safe.directory ${OH_PROJECT_ROOT}"

RUN echo "cd ${OH_PROJECT_ROOT} 2>/dev/null" >> /home/sandbox/.bashrc

Check failure on line 100 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.

Check failure on line 100 in .devcontainer/Dockerfile

View workflow job for this annotation

GitHub Actions / Boot Path Lint (shellcheck + hadolint)

DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.

RUN mkdir -p /.devcontainer && echo '{"workspaceFolder":"/home/sandbox/harness","remoteUser":"sandbox"}' > /.devcontainer/devcontainer.json

LABEL devcontainer.metadata='[{"remoteUser":"sandbox","workspaceFolder":"/home/sandbox/harness"}]'
LABEL org.opencontainers.image.licenses="Apache-2.0"

COPY --chown=sandbox:sandbox .oh/install/ /home/sandbox/install/
RUN chmod +x /home/sandbox/install/*.sh

# boot (entrypoint OH_IMAGE_ONLY / seed_workspace_volume, OH_IMAGE_SEED_SRC).
COPY --chown=sandbox:sandbox . /opt/oh-seed/

ARG INSTALL_PYTHON_KERNEL=true
Expand All @@ -184,11 +110,25 @@
su - sandbox -c "OH_PYTHON_VERSION='${OH_PYTHON_VERSION}' bash /opt/oh-seed/.oh/scripts/provision-python.sh"; \
else \
echo "Skipping Python kernel provisioning (INSTALL_PYTHON_KERNEL=false)"; \
fi
fi \
&& rm -rf /home/sandbox/.cache/uv

FROM base AS final
RUN mkdir -p /.devcontainer && echo '{"workspaceFolder":"/home/sandbox/harness","remoteUser":"sandbox"}' > /.devcontainer/devcontainer.json

LABEL devcontainer.metadata='[{"remoteUser":"sandbox","workspaceFolder":"/home/sandbox/harness"}]'
LABEL org.opencontainers.image.licenses="Apache-2.0"

COPY --chown=sandbox:sandbox . /opt/oh-seed/

COPY .devcontainer/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

COPY --from=home --chown=sandbox:sandbox /home/sandbox /opt/home-seed
RUN rm -rf /opt/home-seed/harness /home/sandbox \
&& chmod 0700 /opt/home-seed \
&& install -d -o sandbox -g sandbox -m 0755 /home/sandbox

WORKDIR ${OH_PROJECT_ROOT}

ENTRYPOINT ["entrypoint.sh"]
Expand Down
3 changes: 0 additions & 3 deletions .devcontainer/client-slack-supervise.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#!/usr/bin/env bash
# the pane pty), with NO `| tee` pipe and NO `--mode rpc`. On a TTY pi resolves to
# session no longer needs `--mode rpc` to avoid the idle exit.
set -u

BACKEND="${GATEWAY_BACKEND:-pi}"
Expand Down Expand Up @@ -76,7 +74,6 @@ while true; do
pkill -f 'pi-messenger-bridge/dist/index.js'; } ) </dev/null >/dev/null 2>&1 &
WD=$!

# flood, stays alive at idle), stderr -> $LOG. No pipe, no --mode rpc.
pi --extension "$BRIDGE_ENTRY" --extension "$RECOVERY_ENTRY" --approve 2>>"$LOG"
rc=$?
else
Expand Down
12 changes: 0 additions & 12 deletions .devcontainer/docker-compose.docker-sock.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
# Docker-socket overlay (opt-in — see docs/security-considerations.md).
#
# Applied by .oh/scripts/docker-compose.sh only when DOCKER_SOCKET is truthy
# (the DOCKER_SOCKET key in .devcontainer/.env).
# Mounting the host Docker socket gives the sandboxed agent effectively host
# root (it can start a privileged container that mounts the host filesystem),
# so enable this ONLY when the agent must drive Docker. entrypoint.sh guards on
# the socket's presence, so the sandbox boots fine whether or not this is applied.
#
# The VS Code "Reopen in Container" path reads docker-compose.yml directly and
# bypasses the wrapper, so it never applies this overlay; add this file to
# devcontainer.json's dockerComposeFile list to enable the socket there.
services:
sandbox:
volumes:
Expand Down
16 changes: 0 additions & 16 deletions .devcontainer/docker-compose.hermes-dashboard.yml

This file was deleted.

59 changes: 4 additions & 55 deletions .devcontainer/docker-compose.image-only.yml
Original file line number Diff line number Diff line change
@@ -1,87 +1,36 @@
# Image-Only Sandbox Docker Compose Configuration (Flavor B, #609)
# ===================================================================
# Standalone recipe for running the PUBLISHED image with no repo checkout
# and no local image build — there is no parent-directory bind mount and
# no build stanza in this file. The workspace and control plane (.oh/,
# agent state) live entirely in the named volume `oh_workspace`, seeded
# from the image's baked /opt/oh-seed on first boot by entrypoint.sh (see
# OH_IMAGE_ONLY handling). Edits made inside the running sandbox persist
# in the volume across image pulls and container recreation, not in the
# image itself.
#
# Usage:
# docker compose -f .devcontainer/docker-compose.image-only.yml up -d
#
# See docs/deployment-prebuilt-image.md for the full Flavor B writeup.

name: ${SANDBOX_NAME:-openharness}

services:
sandbox:
# Always runs the published image — never builds locally.
image: ${OH_SANDBOX_IMAGE:-ghcr.io/mifunedev/openharness:latest}
pull_policy: ${OH_PULL_POLICY:-always}
container_name: ${SANDBOX_NAME:-openharness}
volumes:
- oh_workspace:${OH_PROJECT_ROOT:-/home/sandbox/harness}
- claude-auth:/home/sandbox/.claude
- codex-auth:/home/sandbox/.codex
- pi-auth:/home/sandbox/.pi
- opencode-auth:/home/sandbox/.local/share/opencode
- grok-auth:/home/sandbox/.grok
- deepagents-auth:/home/sandbox/.deepagents
- herdr-data:/home/sandbox/.herdr
- cloudflared-auth:/home/sandbox/.cloudflared
- ssh-config:/home/sandbox/.ssh
# config-dir covers all of ~/.config (gh CLI auth under ~/.config/gh
# included); entrypoint.sh chowns both to the sandbox UID (chmod 700 .ssh).
- config-dir:/home/sandbox/.config
- cc-safety-net:/home/sandbox/.cc-safety-net
- ${OH_HOME_MOUNT:-workspace}:/home/sandbox
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- SANDBOX_NAME=${SANDBOX_NAME:-openharness}
- SANDBOX_PASSWORD=${SANDBOX_PASSWORD:-test1234}
- TZ=${TZ:-America/Los_Angeles}
- CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true
# cc-safety-net destructive-command guard modes (install-decision.md):
# STRICT closes the fail-open hole on unparseable shell; WORKTREE unblocks
# bare reset/clean/checkout inside linked worktrees. No PARANOID/DEBUG;
# CC_SAFETY_NET_OFF left unset (kill-switch, opt-in per session).
- CC_SAFETY_NET_STRICT=1
- CC_SAFETY_NET_WORKTREE=1
- GIT_USER_NAME=${GIT_USER_NAME:-}
- GIT_USER_EMAIL=${GIT_USER_EMAIL:-}
- GH_TOKEN=${GH_TOKEN:-}
- XAI_API_KEY=${XAI_API_KEY:-}
- OH_PROJECT_ROOT=${OH_PROJECT_ROOT:-/home/sandbox/harness}
- OH_IMAGE_ONLY=1
- CRON_AGENT_BIN=${CRON_AGENT_BIN:-claude}
- LANGFUSE_BASE_URL=${LANGFUSE_BASE_URL:-}
- LANGFUSE_PRIVACY_PRESET=${LANGFUSE_PRIVACY_PRESET:-metadata-only}
stdin_open: true
tty: true
init: true
entrypoint: /usr/local/bin/entrypoint.sh
command: sleep infinity
healthcheck:
test: ["CMD", "bash", "${OH_PROJECT_ROOT:-/home/sandbox/harness}/.oh/scripts/sandbox-healthcheck.sh"]
test: ["CMD", "bash", "/home/sandbox/harness/.oh/scripts/sandbox-healthcheck.sh"]
interval: 30s
timeout: 10s
retries: 3
start_period: 300s
start_period: 600s
restart: unless-stopped

volumes:
oh_workspace:
claude-auth:
codex-auth:
pi-auth:
opencode-auth:
grok-auth:
deepagents-auth:
herdr-data:
cloudflared-auth:
ssh-config:
config-dir:
cc-safety-net:
workspace:
19 changes: 0 additions & 19 deletions .devcontainer/docker-compose.ssh.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
# sshd overlay — direct SSH into the sandbox container.
#
# Included automatically by .oh/scripts/docker-compose.sh only when SANDBOX_SSH is truthy
# via `SANDBOX_SSH` in .devcontainer/.env. The host bind
# stays loopback-only (127.0.0.1) so the daemon is reachable from the host and
# through a host-side reverse proxy, but not from the public interface by
# default — switch the bind to 0.0.0.0 for direct remote SSH, or front it with
# nginx for multi-tenant routing. See docs/integrations/sshd.md.
#
# Auth is public-key by default: put your public key(s) in
# SANDBOX_SSH_AUTHORIZED_KEYS (in .devcontainer/.env). Password auth (using
# SANDBOX_PASSWORD) stays off unless SANDBOX_SSH_PASSWORD_AUTH is truthy.
# The entrypoint starts sshd as a background daemon alongside `sleep infinity`.

services:
sandbox:
ports:
- "127.0.0.1:${SANDBOX_SSH_PORT:-2222}:22"
environment:
- SANDBOX_SSH=true
- SANDBOX_SSH_PORT=${SANDBOX_SSH_PORT:-2222}
- SANDBOX_SSH_PASSWORD_AUTH=${SANDBOX_SSH_PASSWORD_AUTH:-false}
- SANDBOX_SSH_AUTHORIZED_KEYS=${SANDBOX_SSH_AUTHORIZED_KEYS:-}
Loading