Skip to content

Repository files navigation

harness

Experimental. Built on OpenShell, which is itself alpha software. Expect breaking changes in both.

Declarative workflow layer for OpenShell AI agent sandboxes.

Quick Start

harness init                        # generate a config
harness doctor                      # check your environment
harness apply -f harness.yaml       # launch a sandbox

Coding agent

Launch an interactive coding session with Claude Code or OpenCode.

harness apply --attach                                        # built-in default agent
harness apply -f harness.yaml --attach                        # agent config in harness.yaml
harness apply -f harness.yaml --attach --entrypoint opencode  # OpenCode

harness apply runs against whichever gateway OpenShell has provisioned and you have selected (openshell gateway select) — local or cluster, the harness YAML is identical. Provisioning the gateway is OpenShell's job, not the harness's (see Install).

One-shot tasks

Run a task headlessly -- the agent executes in a sandbox and outputs results.

harness apply -f harness.yaml --task "review this codebase for security issues"
harness apply -f harness.yaml --task @skills/cpp-pro/SKILL.md

Clone a repo into the sandbox

Use base_agent to inherit providers and inference routing from an existing config. The repo field clones the repository outside the sandbox and uploads it -- OpenShell sandboxes have no host mounts by design.

name: reviewer
base_agent: default
repo: https://github.com/stackrox/collector
task: "identify the highest-priority C++ remediation"
harness apply -f reviewer.yaml

To get results out: --task mode outputs to stdout, openshell sandbox exec pulls files, or attach a github provider so the agent can push directly via the scoped proxy token.

Why this exists

OpenShell provides a strict, secure sandbox runtime — deny-by-default L7 network policy, credential proxying, Landlock filesystem isolation, and inference routing. It also provisions the gateway itself (the local installer, or helm install openshell on a cluster). What it doesn't provide is the developer workflow layer on top: the config that wires up providers, the declarative reconciliation that makes a gateway match your intent, or the CI harness that catches breakage before developers hit it.

Without a shared harness layer, every team building on OpenShell independently solves the same problems — writing shell scripts to register providers, hand-rolling container images, re-deriving inference routing. The configs diverge, the security posture varies, and nobody catches regressions until something breaks in production.

The design boundary: managing a gateway is OpenShell's problem; the harness is a declarative setup/run layer with zero compute-backend opinion. It never provisions or tears down a gateway — it declares providers, inference, and policy against one OpenShell already stood up, and runs agents in it. That keeps the harness YAML portable: the same file targets a local gateway or a cluster gateway with no target field to change.

The core design constraint: if the developer harness isn't running and live-tested in CI, the developer experience can't be maintained. OpenShell, agent CLIs, and provider APIs all change frequently — often multiple times per week. A harness that works today and isn't continuously validated will silently break. harness-openshell runs the workflow (register providers → reconcile inference → create sandbox → run task) in CI on every change, against gateways provisioned three ways: local Podman, Kind, and OpenShift.

The path from local to automated: a developer runs harness apply --attach for interactive work. When the workflow is ready for CI, they change --attach to --task @skill.md and select a cluster gateway instead of the local one. The harness YAML stays the same. No rewriting. The harness YAML is the artifact — sharable, versionable, forkable.

OpenShell's upstream direction is toward a Kubernetes Operator where providers and sandboxes become CRDs and the gateway narrows to data-plane only. The harness explores what the workflow layer looks like above that with a developer mindset from local machine to cluster.

The Agent YAML

A single file defines the entrypoint, credential providers, inference routing, environment, and files uploaded to the sandbox. This is the default config (profiles/agent-default.yaml):

name: agent
entrypoint: claude
tty: true

providers:
  - profile: github                               # scoped GITHUB_TOKEN via proxy
  - profile: google-vertex-ai                     # inference routing through gateway
  - profile: atlassian                            # Jira/Confluence via mcp-atlassian
    env:
      JIRA_URL:                                   # empty = read from host env
      JIRA_USERNAME:
  - profile: google-workspace                     # Gmail, Calendar, Drive via gws CLI

env:
  ANTHROPIC_BASE_URL: https://inference.local     # route inference through gateway proxy
  ANTHROPIC_API_KEY: sk-ant-openshell-proxy-managed
  CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1"

payloads:
  - sandbox_path: /sandbox/.claude/CLAUDE.md      # agent instructions
    local_path: profiles/images/sandbox-default/CLAUDE.md
  - sandbox_path: /sandbox/.claude.json           # claude code settings
    local_path: profiles/images/sandbox-default/claude.json
  - sandbox_path: /sandbox/.claude/settings.json  # permissions and defaults
    local_path: profiles/images/sandbox-default/settings.json
  - sandbox_path: /sandbox/.mcp.json              # MCP server config (jira, confluence)
    local_path: profiles/images/sandbox-default/mcp.json

Credentials never enter the sandbox -- the gateway proxy resolves placeholder tokens at the network boundary. Each provider also contributes its own L7 network policy endpoints and binary allowlists.

Use harness apply -o yaml to see the fully resolved config -- providers expand to show credential definitions, endpoint policies, scopes, and refresh strategies.

Multi-document YAML

Bundle agent, providers, payloads, and policy in one self-contained file. Use base_agent to inherit from an existing config:

---
kind: agent
name: security-reviewer
base_agent: default                               # inherits providers, env, payloads
repo: https://github.com/stackrox/collector
task: "review for memory safety issues"
---
kind: payload
sandbox_path: /sandbox/.claude/CLAUDE.md
content: |
  You are a C++ security review agent specializing in RAII,
  move semantics, and concurrency safety. Focus on the
  highest-priority remediation and explain the fix.
---
kind: policy
network_policies:
  github_git:
    endpoints:
      - host: github.com
        port: 443
        rules:
          - allow: { method: GET, path: "/**/info/refs*" }
          - allow: { method: POST, path: "/**/git-upload-pack" }
    binaries:
      - { path: /usr/bin/git }

This inherits all four providers and inference routing from agent-default.yaml, adds a custom CLAUDE.md as the agent's instructions, and defines an L7 policy that allows git clone but blocks git push at the HTTP method level.

How It Works

(OpenShell has already provisioned the gateway; you selected it)
harness apply -f config.yaml
    |
    +-> Register providers (credentials from host env)
    +-> Reconcile inference routing to match the config
    +-> Upload payloads (CLAUDE.md, MCP config, skills)
    +-> Create sandbox (isolated container, deny-by-default network)
    +-> Run task (agent executes, outputs results)

OpenShell provisions the gateway and provides the runtime isolation. The harness provides the workflow.

For runtime operations and policy management, use openshell directly:

openshell sandbox connect <name>     # interactive shell
openshell sandbox exec <name> -- ... # run commands
openshell sandbox logs <name>        # view logs
openshell policy get <name>          # inspect active policy
openshell term                       # interactive policy terminal

openshell term provides a live view of policy decisions -- which requests are allowed, denied, or pending review. This is how you audit and tune the deny-by-default L7 network policy while an agent is running.

Install

# OpenShell CLI + local gateway, pinned to the version this repo targets
# (.openshell-version). Installs the exact release CI uses and starts the
# managed gateway service (Homebrew/launchd on macOS, systemd on Linux).
make openshell

# Download the harness binary
curl -L https://github.com/stackrox/harness-openshell/releases/latest/download/harness_darwin_arm64 -o harness
chmod +x harness

Install a bare brew install openshell off the tap and you get whatever version the formula defaults to — usually behind. make openshell runs the upstream install.sh at the pinned version instead, so local matches CI exactly.

The installer starts the gateway service; register and select it once:

openshell gateway add https://127.0.0.1:17670 --local --name openshell
openshell gateway select openshell

If you need to restart the service later: brew services restart openshell (macOS) or systemctl --user restart openshell-gateway (Linux).

Or build the harness from source: make cli

On a cluster

Provisioning a cluster gateway is OpenShell's job too — the harness has no deploy command. Install the chart, then register and select the gateway:

helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart
openshell gateway add https://<gateway-endpoint> --name my-cluster
openshell gateway select my-cluster
harness apply -f harness.yaml            # same YAML, cluster gateway

Tear the gateway down with helm uninstall openshell and openshell gateway remove my-cluster. The harness delete command removes sandboxes; add --providers (or --all) to remove providers too. It never removes the gateway.

Migration: harness deploy, harness teardown, harness status, and delete --k8s are removed. Provision the gateway with OpenShell (the openshell installer or helm install openshell); the harness declares providers/inference/policy and runs agents against it.

Reference

Commands

Command What it does
harness init Generate a harness.yaml (interactive or --non-interactive)
harness doctor Validate environment (offline + online checks)
harness apply -f FILE Deploy a sandbox from config
harness apply --task TEXT One-shot headless run
harness apply --task @FILE One-shot from a skill/playbook file
harness apply --attach Interactive TTY mode
harness apply --dry-run Validate without deploying
harness apply -o yaml Output resolved config
harness get agents|providers|gateways List resources
harness describe <name> Sandbox details
harness delete <name> [--all] Tear down
harness plan -f FILE Read-only reconciliation plan (mutates nothing)
harness migrate -f FILE Convert a legacy v1 config to v1alpha1

Credentials

Each provider discovers credentials from the host. Missing providers are skipped.

Provider Required
github GITHUB_TOKEN env var
google-vertex-ai gcloud auth application-default login + ANTHROPIC_VERTEX_PROJECT_ID
atlassian JIRA_API_TOKEN + JIRA_URL + JIRA_USERNAME
google-workspace gws auth login (gws CLI)

Config Files

File Purpose
profiles/agent-*.yaml Agent configs
profiles/providers/ Provider profiles (imported to gateway)
profiles/images/sandbox-default/ Sandbox image defaults (overridable via payloads)

Testing

Tested on macOS (arm64) with Podman. Linux support is expected but not yet validated.

make test             # vet + unit tests (16 packages)
make lint             # golangci-lint
make test-suite       # config parsing (32 tests, no gateway needed)
make test-local       # full e2e on local Podman (22 tests)
make test-kind        # self-contained kind cluster lifecycle
make test-remote      # full e2e on OCP (needs KUBECONFIG)

test-local is the primary validation target. It provisions a gateway via the OpenShell installer, registers all 4 providers, creates sandboxes, verifies exec/env/GWS token resolution/MCP config/Claude inference, tests missing-provider recovery, and tears down the sandboxes and providers it created (the gateway is OpenShell's to remove).

test-kind creates its own kind cluster, helm installs OpenShell, builds and loads the sandbox image, runs the full flow, and deletes the cluster on exit. Use KEEP=1 to keep the cluster for debugging.

test-remote requires KUBECONFIG pointing at an OCP cluster and pushes the image automatically. Use --reuse-gateway to skip gateway provisioning/teardown when iterating.

Each integration target builds (and pushes, for remote) the sandbox image automatically.

Future Work

  • GitHub Action -- run harness tasks in CI (review PRs, enforce standards, generate reports)
  • Observability -- structured telemetry export (Langfuse, MLflow, OpenTelemetry) for agent tool calls, token usage, and policy decisions
  • Skills integration -- first-class support for community skill packs (e.g., awesome-omni-skills) as task inputs
  • OpenShell plugin -- register the harness as an openshell CLI plugin so openshell harness apply works natively alongside other openshell commands
  • Linux validation -- CI and local testing on Linux (currently macOS-only)

Documentation

Document What it is
SPEC.md Behavior spec for the CLI
AGENTS.md Contributor guide
TODO.md Roadmap and upstream tracking

About

Declarative configuration harness for OpenShell agent sandboxes.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages