Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions Tools/harbor-graphcode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__/
.venv/
dist/
uv.lock
88 changes: 88 additions & 0 deletions Tools/harbor-graphcode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# harbor-graphcode

A [Harbor](https://github.com/harbor-framework/harbor) installed agent that runs each
Terminal-Bench task as one GraphCode goal loop, for private trials against Harbor's plain
`claude-code` agent on the same model. It is loaded by import path; nothing is added to
Harbor itself, and nothing here submits to a leaderboard.

## How a task runs

1. **install** — installs zsh (graphcoded launches sessions through `/bin/zsh -i -l`),
Claude Code via the inherited `ClaudeCode.install`, then uploads
`graphcode-linux-<arch>.tar.gz` and unpacks `graphcode`, `graphcoded` and `zmx` into
`/opt/graphcode/bin`.
2. **run** — starts graphcoded with `GRAPHCODE_SUPPORT_DIR=/tmp/gcd`, Claude permission
mode `bypassPermissions` (`IS_SANDBOX=1`), and every model alias pinned to Harbor's
`-m`. It creates one goal loop in the task's working directory with the instruction as
its goal, then polls `graphcode status` until the loop is `succeeded`, `failed`,
`stalled` or `stopped`.
3. **trajectory** — Claude Code transcripts are mirrored into
`/logs/agent/sessions/projects`, where the inherited converter writes `trajectory.json`
and token counts. `/logs/agent/graphcode/` keeps the daemon log, status snapshots and
loop memory.

### Done check

| `done_check` | Loop predicate | Fair against `claude-code`? |
|---|---|---|
| `agent` (default) | None; the loop resolves when the session runs `graphcode node done` | ✅ The same information both agents get |
| `tests` (opt-in) | The task's own `tests/test.sh`, uploaded to `/tests` before the run; passes when it writes reward ≥ 1 | ❌ An oracle: the loop sees the verifier's verdict, plain `claude-code` does not. Only an upper bound. |

The tests directory is read from the trial's `config.json` (or the `tests_dir` kwarg).
Harbor empties `/tests` and re-uploads it before verification, and the check deletes the
reward it wrote, so verification itself is unaffected.

## Build the Linux bundle

Needs docker on the host; not verified yet (no container runtime on the machine this was
written on).

```sh
ARCH=x86_64 Tools/harbor-graphcode/build-linux-bundle.sh
```

Inside `swift:6.2` this runs `swift build -c release --static-swift-stdlib`, builds zmx at
the `ThirdParty/zmx` submodule pin (scgopi/zmx) with zig 0.15.2 `ReleaseFast`, and writes
`Tools/harbor-graphcode/dist/graphcode-linux-x86_64.tar.gz`. The binaries link glibc, so
Alpine task images are refused at install.

## Run

```sh
cd Tools/harbor-graphcode
uv sync
export ANTHROPIC_API_KEY=... # never commit or echo it
export GRAPHCODE_LINUX_BUNDLE_DIR=$PWD/dist

# Smoke: 2 tasks x 1 attempt, both agents
uv run harbor run -d terminal-bench/terminal-bench-2 -e docker -m anthropic/claude-opus-5 \
-a claude-code -l 2 -k 1
uv run harbor run -d terminal-bench/terminal-bench-2 -e docker -m anthropic/claude-opus-5 \
-a harbor_graphcode:GraphCode -l 2 -k 1

# Oracle upper bound, reported separately, never as the comparison
uv run harbor run -d terminal-bench/terminal-bench-2 -e docker -m anthropic/claude-opus-5 \
-a harbor_graphcode:GraphCode --ak done_check=tests -l 2 -k 1
```

Check `harbor run --help` for the task-selection flag your Harbor version uses before
spending. Useful kwargs: `done_check=tests|agent`, `budget_tokens=<n>`,
`poll_interval_sec=<n>`, `bundle_dir=<path>`.

## Tests

No Docker or API key needed:

```sh
uv run --group dev pytest
```

## What is verified

| Piece | Status |
|---|---|
| Adapter logic, generated shell (`bash -n`), awk/Python parser agreement, oracle reward check | ✅ unit tests |
| `graphcode status` line format and a loop reaching `stopped` | ✅ against a real macOS graphcoded (see PR) |
| Linux bundle build in `swift:6.2` | ⚠️ unverified, no container runtime |
| graphcoded launching a Claude session on Linux inside a task image | ⚠️ unverified; CI only smoke-tests CLI verbs on Linux |
| Claude Code's first-run prompts suppressed by the seeded `~/.claude.json` | ⚠️ unverified |
30 changes: 30 additions & 0 deletions Tools/harbor-graphcode/build-in-container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh
# Runs inside swift:6.2 with the repo mounted at /src; see build-linux-bundle.sh.
set -eu
: "${ARCH:?}" "${ZMX_COMMIT:?}" "${ZIG_VERSION:?}"
work=/tmp/gcbundle
rm -rf "$work"
mkdir -p "$work/bundle/bin"

apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl git xz-utils >/dev/null

# A separate build path keeps the host checkout's macOS .build untouched. The static
# stdlib spares task images from needing the Swift runtime; glibc is still required, so
# musl images (Alpine) are out of reach for this bundle.
swift build -c release --static-swift-stdlib --build-path "$work/swift"
cp "$work/swift/release/graphcode" "$work/swift/release/graphcoded" "$work/bundle/bin/"

curl -fsSL "https://ziglang.org/download/$ZIG_VERSION/zig-$ARCH-linux-$ZIG_VERSION.tar.xz" \
| tar -xJ -C "$work"
git clone -q https://github.com/scgopi/zmx.git "$work/zmx"
git -C "$work/zmx" checkout -q "$ZMX_COMMIT"
# ReleaseFast: a Debug zmx is visibly slower than a plain PTY.
(cd "$work/zmx" && "$work/zig-$ARCH-linux-$ZIG_VERSION/zig" build -Doptimize=ReleaseFast)
cp "$work/zmx/zig-out/bin/zmx" "$work/bundle/bin/"

for binary in "$work"/bundle/bin/*; do
echo "== $(basename "$binary")"
ldd "$binary" || true
done
tar -czf "/src/Tools/harbor-graphcode/dist/graphcode-linux-$ARCH.tar.gz" -C "$work/bundle" bin
28 changes: 28 additions & 0 deletions Tools/harbor-graphcode/build-linux-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
# Builds dist/graphcode-linux-<arch>.tar.gz — bin/graphcode, bin/graphcoded and bin/zmx —
# the bundle the Harbor adapter uploads into each task container.
#
# ARCH=x86_64 Tools/harbor-graphcode/build-linux-bundle.sh # Terminal-Bench images
# ARCH=aarch64 Tools/harbor-graphcode/build-linux-bundle.sh
#
# Needs docker (or a docker-compatible CLI) on the host. The arch must match the task
# images, not the host: Terminal-Bench images are linux/amd64, so on Apple Silicon this
# runs emulated and is slow.
set -eu
arch="${ARCH:-x86_64}"
case "$arch" in
x86_64) platform=linux/amd64 ;;
aarch64) platform=linux/arm64 ;;
*) echo "build-linux-bundle: ARCH must be x86_64 or aarch64, got $arch" >&2; exit 2 ;;
esac
root="$(cd "$(dirname "$0")/../.." && pwd)"
# The zmx graphcode ships is the submodule pin (scgopi/zmx, ahead of upstream 0.7.0 with
# the kill-process-group fix), not an upstream release.
zmx_commit="$(git -C "$root" ls-tree HEAD ThirdParty/zmx | awk '{print $3}')"
[ -n "$zmx_commit" ] || { echo "build-linux-bundle: no ThirdParty/zmx pin in HEAD" >&2; exit 1; }
mkdir -p "$root/Tools/harbor-graphcode/dist"
docker run --rm --platform "$platform" \
-v "$root:/src" -w /src \
-e ARCH="$arch" -e ZMX_COMMIT="$zmx_commit" -e ZIG_VERSION="${ZIG_VERSION:-0.15.2}" \
swift:6.2 sh /src/Tools/harbor-graphcode/build-in-container.sh
ls -l "$root/Tools/harbor-graphcode/dist/graphcode-linux-$arch.tar.gz"
3 changes: 3 additions & 0 deletions Tools/harbor-graphcode/harbor_graphcode/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from harbor_graphcode.agent import GraphCode, GraphCodeOptions

__all__ = ["GraphCode", "GraphCodeOptions"]
Loading
Loading