From fc6aec2525ff6869d531ced082954cb5441c147c Mon Sep 17 00:00:00 2001 From: Ryan <10638626+ryancee@users.noreply.github.com> Date: Sun, 19 Apr 2026 02:06:50 -0400 Subject: [PATCH 1/6] fix(harness): use 'opencode run' instead of 'opencode --prompt' for non-interactive execution --- pkg/harness/opencode.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/harness/opencode.go b/pkg/harness/opencode.go index d21b73605..1b2d6a2ec 100644 --- a/pkg/harness/opencode.go +++ b/pkg/harness/opencode.go @@ -60,14 +60,12 @@ func (o *OpenCode) GetEnv(agentName string, agentHome string, unixUsername strin } func (o *OpenCode) GetCommand(task string, resume bool, baseArgs []string) []string { - args := []string{"opencode"} + args := []string{"opencode", "run"} if resume { args = append(args, "--continue") - } else { - args = append(args, "--prompt") - if task != "" { - args = append(args, task) - } + } + if task != "" { + args = append(args, task) } args = append(args, baseArgs...) From 87bd37e88e70523635ff001a46e94ca966bfcdff Mon Sep 17 00:00:00 2001 From: Ryan <10638626+ryancee@users.noreply.github.com> Date: Sun, 19 Apr 2026 03:35:21 -0400 Subject: [PATCH 2/6] feat(harness): add pi harness and fix symlink traversal for harness-config - Add Pi harness (pkg/harness/pi.go) supporting @mariozechner/pi-coding-agent with --print for non-interactive execution and --model flag passthrough - Add pi container image build (image-build/pi/Dockerfile) - Register Pi in harness registry and update harness count in tests - Add PiAuthFile to AuthConfig in pkg/api/types.go - Fix pkg/transfer/collect.go: resolve symlink base paths via EvalSymlinks so that stow-managed harness-config directories are correctly scanned - Fix pkg/config/harness_config.go: follow symlink directories in loadHarnessConfigsFromDir so stow-symlinked configs appear in listings --- image-build/cloudbuild-harnesses.yaml | 23 ++++ image-build/pi/Dockerfile | 26 ++++ image-build/scripts/build-images.sh | 2 +- pkg/api/types.go | 1 + pkg/config/harness_config.go | 9 +- pkg/harness/harness.go | 3 + pkg/harness/harness_test.go | 3 +- pkg/harness/pi.go | 184 ++++++++++++++++++++++++++ pkg/harness/pi/embeds.go | 20 +++ pkg/harness/pi/embeds/config.yaml | 17 +++ pkg/harness/pi_test.go | 179 +++++++++++++++++++++++++ pkg/transfer/collect.go | 11 +- 12 files changed, 473 insertions(+), 5 deletions(-) create mode 100644 image-build/pi/Dockerfile create mode 100644 pkg/harness/pi.go create mode 100644 pkg/harness/pi/embeds.go create mode 100644 pkg/harness/pi/embeds/config.yaml create mode 100644 pkg/harness/pi_test.go diff --git a/image-build/cloudbuild-harnesses.yaml b/image-build/cloudbuild-harnesses.yaml index a9f8d80a1..aeb802518 100644 --- a/image-build/cloudbuild-harnesses.yaml +++ b/image-build/cloudbuild-harnesses.yaml @@ -121,6 +121,29 @@ steps: env: - 'DOCKER_CLI_EXPERIMENTAL=enabled' + # Build Pi Harness Image + - name: 'gcr.io/cloud-builders/docker' + id: 'build-scion-pi' + dir: 'image-build/pi' + args: + - 'buildx' + - 'build' + - '--platform' + - 'linux/amd64,linux/arm64' + - '--build-arg' + - 'BASE_IMAGE=$_REGISTRY/scion-base:latest' + - '-t' + - '$_REGISTRY/scion-pi:$_SHORT_SHA' + - '-t' + - '$_REGISTRY/scion-pi:latest' + - '-f' + - 'Dockerfile' + - '--pull' + - '--push' + - '.' + env: + - 'DOCKER_CLI_EXPERIMENTAL=enabled' + substitutions: _REGISTRY: 'us-central1-docker.pkg.dev/${PROJECT_ID}/public-docker' options: diff --git a/image-build/pi/Dockerfile b/image-build/pi/Dockerfile new file mode 100644 index 000000000..910749f69 --- /dev/null +++ b/image-build/pi/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ARG BASE_IMAGE +FROM ${BASE_IMAGE} + +RUN mkdir -p /home/scion/.pi/agent && \ + chown -R scion:scion /home/scion/.pi + +# Install pi coding agent +RUN npm install -g @mariozechner/pi-coding-agent \ + && npm cache clean --force + +CMD ["pi"] diff --git a/image-build/scripts/build-images.sh b/image-build/scripts/build-images.sh index 8f49b6eaa..6c5ac4baa 100755 --- a/image-build/scripts/build-images.sh +++ b/image-build/scripts/build-images.sh @@ -35,7 +35,7 @@ PUSH="" PLATFORM="" TAG="latest" -HARNESSES=(claude gemini opencode codex) +HARNESSES=(claude gemini opencode codex pi) usage() { cat < Date: Sun, 19 Apr 2026 03:43:42 -0400 Subject: [PATCH 3/6] fix(harness/pi): allow no-auth for local model configurations Pi can run without API credentials when using locally-configured models via models.json (e.g. oMLX server). Return method=none instead of error when no auth credentials are found. --- pkg/harness/pi.go | 4 +++- pkg/harness/pi_test.go | 11 +++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/harness/pi.go b/pkg/harness/pi.go index a76826926..62dc0153d 100644 --- a/pkg/harness/pi.go +++ b/pkg/harness/pi.go @@ -180,5 +180,7 @@ func (p *Pi) ResolveAuth(auth api.AuthConfig) (*api.ResolvedAuth, error) { }, nil } - return nil, fmt.Errorf("pi: no valid auth method found; set ANTHROPIC_API_KEY or OPENAI_API_KEY, or provide auth credentials at ~/.pi/agent/auth.json") + // No credentials found — pi can still run with locally-configured models + // (e.g. via ~/.pi/agent/models.json pointing at a local oMLX/OpenAI-compat server). + return &api.ResolvedAuth{Method: "none"}, nil } diff --git a/pkg/harness/pi_test.go b/pkg/harness/pi_test.go index c3bf77f71..c8aeb6afd 100644 --- a/pkg/harness/pi_test.go +++ b/pkg/harness/pi_test.go @@ -17,7 +17,6 @@ package harness import ( "os" "path/filepath" - "strings" "testing" "github.com/GoogleCloudPlatform/scion/pkg/api" @@ -138,12 +137,12 @@ func TestPiResolveAuth_PreferenceOrder(t *testing.T) { func TestPiResolveAuth_NoCreds(t *testing.T) { p := &Pi{} - _, err := p.ResolveAuth(api.AuthConfig{}) - if err == nil { - t.Fatal("expected error for empty AuthConfig") + result, err := p.ResolveAuth(api.AuthConfig{}) + if err != nil { + t.Fatalf("expected no error for empty AuthConfig (local models supported): %v", err) } - if !strings.Contains(err.Error(), "ANTHROPIC_API_KEY") { - t.Errorf("error should mention ANTHROPIC_API_KEY: %v", err) + if result.Method != "none" { + t.Errorf("expected method %q for no-creds case, got %q", "none", result.Method) } } From 788c738bd680c95615e639ac83e032fa09240a90 Mon Sep 17 00:00:00 2001 From: Ryan <10638626+ryancee@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:17:34 -0400 Subject: [PATCH 4/6] pi image: clone pi-skills and run bun install for brave-search, browser-tools, youtube-transcript --- image-build/pi/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/image-build/pi/Dockerfile b/image-build/pi/Dockerfile index 910749f69..307dcd629 100644 --- a/image-build/pi/Dockerfile +++ b/image-build/pi/Dockerfile @@ -23,4 +23,11 @@ RUN mkdir -p /home/scion/.pi/agent && \ RUN npm install -g @mariozechner/pi-coding-agent \ && npm cache clean --force +# Clone pi-skills and pre-install JS dependencies for skills that require it +RUN git clone --depth=1 https://github.com/badlogic/pi-skills /home/scion/.pi/agent/skills/pi-skills && \ + cd /home/scion/.pi/agent/skills/pi-skills/brave-search && bun install && \ + cd /home/scion/.pi/agent/skills/pi-skills/browser-tools && bun install && \ + cd /home/scion/.pi/agent/skills/pi-skills/youtube-transcript && bun install && \ + chown -R scion:scion /home/scion/.pi + CMD ["pi"] From 113e0da0de3140a85dd7218937f795f19c19b0eb Mon Sep 17 00:00:00 2001 From: Ryan <10638626+ryancee@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:16:22 -0400 Subject: [PATCH 5/6] scion-pi: install gccli, gdcli, gmcli globals via bun --- image-build/pi/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/image-build/pi/Dockerfile b/image-build/pi/Dockerfile index 307dcd629..1dc402843 100644 --- a/image-build/pi/Dockerfile +++ b/image-build/pi/Dockerfile @@ -23,6 +23,9 @@ RUN mkdir -p /home/scion/.pi/agent && \ RUN npm install -g @mariozechner/pi-coding-agent \ && npm cache clean --force +# Install gccli, gdcli, gmcli globally (BUN_INSTALL=/usr/local puts binaries in /usr/local/bin) +RUN BUN_INSTALL=/usr/local bun add -g @mariozechner/gccli @mariozechner/gdcli @mariozechner/gmcli + # Clone pi-skills and pre-install JS dependencies for skills that require it RUN git clone --depth=1 https://github.com/badlogic/pi-skills /home/scion/.pi/agent/skills/pi-skills && \ cd /home/scion/.pi/agent/skills/pi-skills/brave-search && bun install && \ From 68843f34eead914e0551557dd38122e2233bd795 Mon Sep 17 00:00:00 2001 From: Ryan <10638626+ryancee@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:50:31 -0400 Subject: [PATCH 6/6] feat: add pi-generic template using generic harness with pi --print Creates .scion/templates/pi-generic/ template that uses harness: generic with command_args: [pi, --print] and image: scion-pi:latest. This validates that the generic harness correctly proxies the pi CLI, producing the same invocation as the native pi harness. --- .../harness-configs/generic/config.yaml | 20 +++++++++++++++++++ .scion/templates/pi-generic/scion-agent.yaml | 17 ++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .scion/templates/pi-generic/harness-configs/generic/config.yaml create mode 100644 .scion/templates/pi-generic/scion-agent.yaml diff --git a/.scion/templates/pi-generic/harness-configs/generic/config.yaml b/.scion/templates/pi-generic/harness-configs/generic/config.yaml new file mode 100644 index 000000000..8c52d2b5b --- /dev/null +++ b/.scion/templates/pi-generic/harness-configs/generic/config.yaml @@ -0,0 +1,20 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +harness: generic +image: scion-pi:latest +user: scion +command_args: + - pi + - --print diff --git a/.scion/templates/pi-generic/scion-agent.yaml b/.scion/templates/pi-generic/scion-agent.yaml new file mode 100644 index 000000000..730c68364 --- /dev/null +++ b/.scion/templates/pi-generic/scion-agent.yaml @@ -0,0 +1,17 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +schema_version: "1" +description: "Pi agent via generic harness (pass-through validation)" +default_harness_config: generic