Behavior specification for the OpenShell Harness CLI.
The harness declares providers, inference routing, and policy, then creates and
runs AI agent sandboxes against a gateway OpenShell has already provisioned.
Provisioning a gateway (local installer, or helm install openshell on a
cluster) is OpenShell's job — the harness has zero compute-backend opinion and
never deploys or tears down a gateway. It runs against whichever gateway is
selected (openshell gateway select), local or cluster, from the same YAML.
Each sandbox is an isolated container running an agent entrypoint (e.g. Claude Code, OpenCode, or Codex; bash or any binary on PATH also works), with credential providers, network policies, and a rendered payload (task.md and a bin/ directory).
Requires OpenShell v0.0.110+.
Agent configs live in profiles/agent-<name>.yaml. Each declares the sandbox image, entrypoint, providers, and environment:
name: agent
entrypoint: claude # or: opencode
tty: true
providers:
- profile: github
- profile: google-vertex-ai
- profile: atlassian
env:
JIRA_URL:
JIRA_USERNAME:
- profile: google-workspace
env:
ANTHROPIC_BASE_URL: https://inference.localFields:
name(required) -- sandbox name, used foropenshell sandbox connectbase_agent-- name of a base agent config to inherit from (e.g.,defaultresolvesagent-default.yaml). Providers, env, and payloads are merged additively; scalar fields (entrypoint, repo, task, image, policy) from the overlay win when non-empty.image-- container image for the sandbox (default: version-matched fromquay.io/rcochran/openshell, override withHARNESS_OS_IMAGEenv)entrypoint-- command to run (default:claude). Supportsclaude,codex,opencode,bash, or any binary on PATH.tty-- enable TTY (default: true)repo-- git URL to clone outside the sandbox and upload to/sandbox/<repo-name>. Shallow clone (--depth 1) with submodules. Git credentials never enter the sandbox unless needed.repo_ref-- branch, tag, or ref to clone (default: HEAD). Passed as--branchto git clone.task-- path to a task.md file, passed to entrypoint via-p "$(cat task.md)"providers-- list of provider profile referencesproviders[].profile-- OpenShell provider profile nameproviders[].env-- non-secret env vars for this provider (resolved viaos.ExpandEnv; empty values read from host env; injected via--envon sandbox create)env-- additional environment variables injected via--envon sandbox create (empty values read from host env)include-- extra files to include in the payloadpolicy-- path to a network policy YAML
The agent config names no gateway: harness apply runs against the gateway
OpenShell has provisioned and you have selected (openshell gateway select).
Provider profiles live in profiles/providers/. These are imported to the gateway during provider registration.
Agent configs support multi-document YAML (--- separated) where provider and policy definitions are co-located in one file:
---
kind: agent
name: my-agent
entrypoint: claude
providers:
- profile: github
---
kind: provider
name: github
type: github
credentials: [GITHUB_TOKEN]
---
kind: policy
network_policies:
github_git:
endpoints:
- host: github.com
port: 443Documents are dispatched by kind field. No kind field = agent (backwards compatible). Definitions in the harness file take priority over the profiles/ tree.
harness apply [-f FILE] [--agent NAME] [--name SANDBOX] [--task TEXT|@FILE] [--entrypoint CMD] [--attach] [--setup-only] [--dry-run] [-o yaml|json]
Primary command. Resolves an agent config, reconciles providers and inference on the selected gateway, creates a sandbox. It never provisions a gateway — one must already be provisioned by OpenShell and selected.
- Parse agent config -- resolve
agent-<name>.yamlfrom harness directory (default:default).-foverrides with a direct file path. Falls back to embeddedagent-basic.yamlwhenagent-default.yamlis not found on disk. - Check output mode -- if
-o yamlor-o json, render the fully resolved config and exit. No gateway interaction needed. - Check version -- warn if openshell CLI is below v0.0.110.
- Require an active gateway -- resolve the gateway from
openshell's active selection (OPENSHELL_GATEWAYenv var overrides). Error up front if none is selected;upLocaladditionally preflights that the gateway is reachable before touching providers or creating a sandbox. - Dry-run check -- if
--dry-run, validate each step (gateway reachable, providers resolvable, env vars resolved, image available) and exit with pass/fail report. - Ensure providers -- auto-register missing providers. Three registration flows:
- Standard (
--from-existing): GitHub, Atlassian -- OpenShell discovers credentials from local env. - ADC (
--from-gcloud-adc): Vertex AI -- reads ADC file, configures inference routing. - Custom: GWS -- multi-step OAuth refresh flow.
- Standard (
- Render payload --
task.md(if set) and abin/directory. The in-sandbox command is built by the agent adapter (internal/agent/adapter.go) as abash -lcinvocation (PATH setup, entrypoint validation viacommand -v), not arun.shfile. Task dispatch depends on mode and entrypoint: headless (default) usesopencode run "$(cat task.md)"for OpenCode and--print "$(cat task.md)"for claude/codex/custom entrypoints; interactive (--attach) uses-p "$(cat task.md)". - Create sandbox --
openshell sandbox createwith--env(env vars),--upload(payload), and startup command. Retry up to 5 times.
Default is non-interactive (headless). Use --attach for TTY mode.
--setup-only reconciles providers/inference on the selected gateway, then stops before creating a sandbox or running the agent.
List resources. Wraps openshell list commands with consistent structured output across resource types. -o table is the default. Credential values are never included in structured output.
| Subcommand | Aliases | What it lists |
|---|---|---|
get agents |
sandboxes, sandbox |
Running sandboxes (name, phase) |
get providers |
provider |
Registered providers (name only, no credentials) |
get gateways |
gateway, gw |
Gateways (name, endpoint, active) |
These are convenience wrappers. For full details, use openshell sandbox list, openshell provider list, etc. directly.
Show detailed status for a specific sandbox: phase, active gateway, and registered providers.
Delete sandboxes by name, or use flags for bulk operations. --all deletes sandboxes and providers. It never removes the gateway: cluster teardown is helm uninstall openshell plus openshell gateway remove.
Generate a harness.yaml config file. Interactive by default (prompts for entrypoint and providers); --non-interactive writes the embedded default. Writes to harness.yaml unless -o overrides the path. The generated config names no gateway — select one with openshell gateway select.
Validate the environment for a configured sandbox. Phase 1 (offline) checks the openshell binary and provider credentials without a running gateway; Phase 2 (online) checks provider registration when the gateway is reachable.
Read-only reconciliation plan. Shows the actions harness apply would take without mutating anything. Distinct from apply --dry-run, which is a separate legacy path.
Convert a legacy v1 harness config to the v1alpha1 format. The input YAML is normalized and written as v1alpha1 to stdout (or -o FILE). Fields with no v1alpha1 home (task, include, inline policy documents, unresolved base_agent) are reported as warnings on stderr. The legacy gateway: field named a deploy profile, a concept the harness no longer owns; it is dropped, leaving spec.target.gateway empty. The active gateway is chosen outside the config — with openshell gateway select or $OPENSHELL_GATEWAY — not by re-adding a field to the YAML.
| File | Purpose |
|---|---|
profiles/agent-*.yaml |
Agent config: image, entrypoint, providers, env, task |
profiles/providers/ |
OpenShell provider profile YAMLs |
profiles/images/sandbox-default/Dockerfile |
Sandbox image: OpenShell base + MCP servers + CLI tools |
profiles/images/sandbox-default/CLAUDE.md |
Claude Code project instructions for sandbox |
profiles/images/sandbox-default/claude.json |
Claude Code settings |
profiles/images/sandbox-default/mcp.json |
MCP server config for Claude agent |
profiles/images/sandbox-default/opencode.json |
MCP server config for OpenCode agent |
profiles/images/sandbox-default/policy.yaml |
Network egress rules applied to sandboxes |
profiles/images/sandbox-default/settings.json |
Claude Code settings overlay |
All images are published to quay.io/rcochran/openshell. CI never publishes floating tags (:latest, :sandbox); the bare :sandbox fallback below exists only for local go build binaries without version ldflags.
| Trigger | Sandbox |
|---|---|
Release v0.1.2 |
:sandbox-v0.1.2 |
| Any push/PR | :sandbox-<sha> |
The CLI resolves images from its embedded version (set via -ldflags at build time):
v0.1.2->:sandbox-v0.1.2(tagged release)v0.1.2-5-gabc1234->:sandbox-v0.1.2-5-gabc1234(dev build, matchesmake dev-sandbox)dev->:sandbox(barego buildwithout ldflags)
HARNESS_OS_IMAGE env var overrides the version-based resolution.
Harness-specific variables use the HARNESS_OS_ prefix. OpenShell runtime variables use OPENSHELL_.
| Variable | Purpose |
|---|---|
HARNESS_OS_DIR |
Override harness directory detection |
HARNESS_OS_IMAGE |
Override sandbox image (dev/CI builds) |
OPENSHELL_CLI |
Override openshell binary path |
OPENSHELL_GATEWAY |
Override the active gateway name (used by apply, plugin-compatible) |
OPENSHELL_MODEL |
Inference model for provider registration (default: claude-sonnet-4-6) |
The harness renders agent config into a self-contained payload uploaded to /sandbox/.config/openshell/:
openshell/
task.md -- task file with envsubst applied (if task: is set)
bin/ -- payload binaries prepended to PATH
Environment variables are injected directly via --env KEY=VALUE flags on openshell sandbox create -- no file upload needed for env vars. The in-sandbox command (entrypoint validation and exec, with -p/--print task) is built by the agent adapter and wrapped in bash -lc; there is no run.sh file.