Skip to content
Merged
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
93 changes: 93 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
language: en-US
reviews:
profile: chill
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: false
knowledge_base:
learnings:
scope: auto
issues:
scope: auto
jira:
project_keys: []
linear:
team_keys: []
path_instructions:
- path: "**/*.go"
instructions: |
This is a Go CLI ("harness") that wraps the OpenShell CLI to deploy
AI agent sandboxes. The harness orchestrates gateway deployment,
provider registration, payload upload, and sandbox creation.

Key architecture:
- cmd/executor.go: main apply flow (upLocal)
- cmd/providers.go: provider registration (registerStandard, registerADC, registerGWS)
- cmd/resolve.go: config resolution, base_agent merging, gateway auto-discovery
- internal/agent/agent.go: AgentConfig parsing, MergeOver, BuildEnvMap, RenderPayload
- internal/gateway/: Gateway interface + CLI implementation (shells out to openshell)

Review focus:
- Correct error handling (wrap with context, don't swallow)
- CLI argument construction (exec.Command, not shell)
- Credential handling (never log secrets, never pass via CLI args if avoidable)
- Gateway interface usage (code should go through the Gateway interface, not call openshell directly)
- base_agent merge semantics (overlay wins for scalars, additive for providers/env)
- repo clone (happens outside sandbox, git creds never enter unless needed)
- path: "SPEC.md"
instructions: |
This is the authoritative behavior spec for the harness CLI.
All field names, command behavior, and config semantics must
match the Go implementation. Flag changes in code that aren't
reflected here, or spec claims that don't match the code.
- path: "AGENTS.md"
instructions: |
Contributor guide. Check that coding conventions, test
instructions, and upstream references are current.
- path: "profiles/agent-*.yaml"
instructions: |
Agent config files. These define what runs in a sandbox.
Key fields: name, base_agent, entrypoint, repo, repo_ref,
providers (with profile refs and env overrides), env, payloads,
task, gateway, policy, image. Empty env values read from host
environment. Check consistency with SPEC.md field definitions.
- path: "profiles/providers/**"
instructions: |
OpenShell provider profile YAMLs imported to the gateway.
These define credential discovery (env_vars), auth style
(bearer/basic), refresh strategies (oauth2-refresh-token),
L7 network policy endpoints, and binary allowlists.
Check that credential env_vars match what cmd/providers.go expects.
Check that endpoint hosts and ports are correct for the service.
- path: "profiles/gateways/**"
instructions: |
Gateway deployment profiles. Current targets:
- local-container.yaml (Podman, type: local)
- helm.yaml (kind cluster, type: remote, service: nodeport)
- openshift.yaml (OCP, type: remote, service: route)
New gateways are auto-discovered by scanning this directory.
- path: "profiles/images/sandbox-default/**"
instructions: |
Sandbox image files: Dockerfile, CLAUDE.md (agent instructions),
claude.json/settings.json (Claude Code config), mcp.json (MCP
server config for Jira/Confluence), opencode.json, policy.yaml
(L7 network policy with per-provider endpoint rules).
These are the defaults — overridable via payloads in agent configs.
- path: "test/**"
instructions: |
Integration test scripts. Gateway names must match current
naming (local-container, helm, openshift). test-flow.sh is
the primary e2e test. kind-lifecycle.sh creates its own cluster.
Check that test assertions match current CLI output format.
- path: ".github/workflows/**"
instructions: |
CI workflows. images.yml pushes to quay.io/rcochran/openshell
using REGISTRY_QUAY_USER/REGISTRY_QUAY_PASSWORD secrets.
integration.yml runs test-local (Dockerfile path for image),
test-kind (Docker build + kind load), and config suite.
The local job uses HARNESS_OS_IMAGE=profiles/images/sandbox-default
so openshell builds from Dockerfile directly.
chat:
auto_reply: true
134 changes: 67 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ 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.

```bash
harness apply --attach # local Podman with built-in harness
harness apply -f harness.yaml --attach --gateway openshift # Agent config in harness.yaml on OpenShift
harness apply -f harness.yaml --attach --entrypoint opencode # OpenCode
```

### One-shot tasks

Run a task headlessly -- the agent executes in a sandbox and outputs results.
Expand All @@ -23,9 +33,7 @@ harness apply -f harness.yaml --task @skills/cpp-pro/SKILL.md

### Clone a repo into the sandbox

The `repo` field clones a repository outside the sandbox and uploads it. Git credentials never enter the sandbox unless needed.

Use `base_agent` to inherit providers, env, and payloads from an existing config — you only specify what's different:
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.

```yaml
name: reviewer
Expand All @@ -34,112 +42,92 @@ repo: https://github.com/stackrox/collector
task: "identify the highest-priority C++ remediation"
```

This inherits everything from `agent-default.yaml` (providers, inference routing, payloads) and adds the repo and task. Without `base_agent`, you'd need to specify the inference provider and env vars yourself.

```bash
harness apply -f reviewer.yaml
```

### Getting results out

The agent runs in an isolated sandbox. To extract results:

```bash
# Agent outputs to stdout (--task mode)
harness apply -f harness.yaml --task "summarize the codebase" > results.md

# Pull a specific file from the sandbox
openshell sandbox exec <name> -- cat /sandbox/collector/report.md > report.md

# Extract a diff
openshell sandbox exec <name> -- git -C /sandbox/collector diff > changes.patch

# Download files
openshell sandbox exec <name> -- tar czf - /sandbox/collector/output/ > output.tar.gz
```

If the `github` provider is attached, the agent can push directly -- the proxy provides a scoped `GITHUB_TOKEN` without exposing raw credentials.

### Coding agent

Launch an interactive coding session with Claude Code or OpenCode.

```bash
# Local (Podman)
harness apply -f harness.yaml --attach

# On OpenShift
harness apply -f harness.yaml --attach --gateway openshift

# OpenCode instead of Claude
harness apply -f harness.yaml --attach --entrypoint opencode
```
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.
Comment thread
robbycochran marked this conversation as resolved.

## Why this exists

[OpenShell](https://github.com/NVIDIA/OpenShell) is a foundation layer -- sandboxed containers with deny-by-default L7 network policy, credential proxy, Landlock filesystem isolation, and inference routing. It is designed as a strict, secure base that other tooling builds workflows on top of.
[OpenShell](https://github.com/NVIDIA/OpenShell) is a sandbox management layer with deny-by-default L7 network policy, credential proxy, filesystem isolation, and inference routing. It is designed as a strict, secure base that supports other workflows.

The harness is one such workflow layer. One YAML file defines the agent, providers, payloads, and policy. One command deploys it -- locally via Podman or remotely on Kubernetes.
One YAML file defines the agent, providers, payloads, and policy and one command deploys it via Podman or remotely on Kubernetes.

OpenShell's upstream direction is toward a [Kubernetes Operator](https://github.com/NVIDIA/OpenShell/issues/1719) 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 -- and covers the local Podman development path that no operator will own.
OpenShell's upstream direction is toward a [Kubernetes Operator](https://github.com/NVIDIA/OpenShell/issues/1719) 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 what runs, what credentials it gets, and what files are uploaded to the sandbox.
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`):

```yaml
name: agent
entrypoint: claude
tty: true
repo: https://github.com/stackrox/collector # cloned outside sandbox, uploaded in

providers:
- profile: github
- profile: google-vertex-ai
- profile: atlassian
- 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: ${JIRA_URL}
JIRA_USERNAME: ${JIRA_USERNAME}
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
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
- sandbox_path: /sandbox/.claude/CLAUDE.md # agent instructions
local_path: profiles/images/sandbox-default/CLAUDE.md
- sandbox_path: /sandbox/.mcp.json
- 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 file:
Bundle agent, providers, payloads, and policy in one self-contained file. Use `base_agent` to inherit from an existing config:

```yaml
---
kind: agent
name: cpp-reviewer
entrypoint: claude
providers:
- profile: github
---
kind: provider
name: github
type: github
credentials: [GITHUB_TOKEN]
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.
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:
github_git:
endpoints:
- { host: "api.github.com", port: 443 }
- 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

```
Expand All @@ -154,21 +142,25 @@ harness apply -f config.yaml

OpenShell provides the runtime isolation. The harness provides the workflow.

For runtime operations, use openshell directly:
For runtime operations and policy management, use openshell directly:
```bash
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

```bash
# macOS
brew tap nvidia/openshell && brew install openshell && brew services start openshell

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

Expand Down Expand Up @@ -234,6 +226,14 @@ make test-remote # full e2e on OCP (needs KUBECONFIG)

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](https://github.com/diegosouzapw/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 |
Expand Down
4 changes: 2 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ providers:
- profile: google-vertex-ai
- profile: atlassian
env:
JIRA_URL: ${JIRA_URL}
JIRA_USERNAME: ${JIRA_USERNAME}
JIRA_URL:
JIRA_USERNAME:
- profile: google-workspace

env:
Expand Down
6 changes: 3 additions & 3 deletions cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"strings"
"time"

"github.com/robbycochran/harness-openshell/internal/agent"
"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/stackrox/harness-openshell/internal/agent"
"github.com/stackrox/harness-openshell/internal/gateway"
"github.com/stackrox/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

Expand Down
6 changes: 3 additions & 3 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package cmd
import (
"fmt"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/k8s"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/stackrox/harness-openshell/internal/gateway"
"github.com/stackrox/harness-openshell/internal/k8s"
"github.com/stackrox/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

Expand Down
6 changes: 3 additions & 3 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"strings"
"time"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/k8s"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/stackrox/harness-openshell/internal/gateway"
"github.com/stackrox/harness-openshell/internal/k8s"
"github.com/stackrox/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

Expand Down
4 changes: 2 additions & 2 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"path/filepath"
"testing"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/k8s"
"github.com/stackrox/harness-openshell/internal/gateway"
"github.com/stackrox/harness-openshell/internal/k8s"
)

func setupDeployHarnessDir(t *testing.T) string {
Expand Down
4 changes: 2 additions & 2 deletions cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package cmd
import (
"fmt"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/stackrox/harness-openshell/internal/gateway"
"github.com/stackrox/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

Expand Down
Loading
Loading