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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install bats
run: sudo apt-get install -y bats
- run: go vet ./...
- run: CGO_ENABLED=0 go test ./...
- name: Build binary for bats
run: CGO_ENABLED=0 go build -o harness .
- run: bats test/preflight.bats

lint:
runs-on: ubuntu-latest
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ jobs:
tags: |
type=semver,pattern=sandbox-v{{version}}
type=sha,prefix=sandbox-,format=short
# Fork PRs can't write to the registry; same-repo PRs push so the
# integration (kind) job can pull the image without a local preload.
- uses: docker/build-push-action@v6
with:
context: sandbox
platforms: linux/amd64,linux/arm64
push: true
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache
cache-to: type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache,mode=max
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}:sandbox-cache,mode=max', env.IMAGE_BASE) || '' }}

runner:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -73,8 +75,8 @@ jobs:
with:
context: build/runner
platforms: linux/amd64
push: true
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:runner-cache
cache-to: type=registry,ref=${{ env.IMAGE_BASE }}:runner-cache,mode=max
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}:runner-cache,mode=max', env.IMAGE_BASE) || '' }}
18 changes: 11 additions & 7 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ jobs:
with:
go-version-file: go.mod

- name: Install bats
run: sudo apt-get install -y bats

- name: Install openshell
run: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh

Expand Down Expand Up @@ -56,15 +53,22 @@ jobs:
with:
go-version-file: go.mod

- name: Install bats
run: sudo apt-get install -y bats

- name: Install openshell
run: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh

- name: Wait for gateway
run: |
for i in $(seq 1 30); do
openshell inference get &>/dev/null && break
sleep 1
done
openshell gateway list

# install_only: kind-lifecycle.sh creates its own cluster with an
# isolated kubeconfig — only the kind binary is needed here.
- uses: helm/kind-action@v1
with:
cluster_name: openshell
install_only: true

- name: Install helm
uses: azure/setup-helm@v4
Expand Down
7 changes: 6 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
version: 2

# Asset names match the README install instructions:
# releases/latest/download/harness_darwin_arm64
project_name: harness

builds:
- binary: harness
env:
Expand All @@ -14,4 +18,5 @@ builds:
- -s -w -X main.version={{.Version}}

archives:
- format: tar.gz
- formats: ["binary"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
13 changes: 9 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,19 @@ Runs in GitHub Actions on every PR.
--ci flag = --no-providers --agent=ci
```

CI mode also auto-activates when the `CI` env var is `true` (set by GitHub Actions).

### Make targets

See `make help` for the full list. The test entry points:

| Target | Gateway | Mode |
|--------|---------|------|
| `make validate-local` | local Podman | default (needs creds) |
| `make validate-local-ci` | local Podman | ci (no creds) |
| `make validate-kind` | kind cluster | default (needs creds) |
| `make validate-kind-ci` | kind cluster | ci (no creds) |
| `make test` | none | vet + unit tests + bats |
| `make test-local` | local Podman | default locally, ci on GHA |
| `make test-kind` | kind cluster | default locally, ci on GHA |
| `make test-remote` | OCP | default (needs KUBECONFIG + creds) |
| `make test-all` | all of the above | |

Or directly:
```bash
Expand Down
55 changes: 19 additions & 36 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
## OpenShell Harness — build, push, and test
##
## Tests (CI mode auto-detects from CI env var):
## make test # unit + bats + lint (~2min)
## make test-local # test + local gateway integration
## make test-kind # test + kind (self-contained cluster)
## make test-remote # test + OCP (needs KUBECONFIG)
## make test-all # all of the above
## make test # vet + unit + bats (~2min)
## make test-local # local gateway integration
## make test-kind # kind integration (self-contained cluster)
## make test-remote # OCP integration (needs KUBECONFIG)
## make test-all # unit + all integrations
##
## Images:
## make sandbox # build + push sandbox (multi-arch)
## make runner # build + push runner
## Images (dev builds, tagged from git describe):
## make dev-sandbox # build sandbox image (native arch)
## make dev-runner # build runner image
## make dev-push # build + push both (sandbox multi-arch)
## Release images are built and pushed by .github/workflows/images.yml.

REGISTRY ?= ghcr.io/robbycochran/harness-openshell
CONTAINER_CLI ?= podman
Expand All @@ -20,7 +22,7 @@ LDFLAGS := -s -w -X main.version=$(VERSION)
DEV_SANDBOX_IMAGE := $(REGISTRY):sandbox-$(VERSION)
DEV_RUNNER_IMAGE := $(REGISTRY):runner-$(VERSION)

.PHONY: all cli sandbox push-sandbox cli-runner runner push-runner \
.PHONY: all cli cli-runner \
vet lint test test-local test-kind test-remote test-all \
dev-sandbox dev-runner dev-push clean help

Expand All @@ -36,30 +38,11 @@ cli:

## ── Images ────────────────────────────────────────────────────────────

## Sandbox image (Claude Code + mcp-atlassian + gws, multi-arch)
sandbox: sandbox/Dockerfile sandbox/startup.sh \
sandbox/policy.yaml sandbox/CLAUDE.md sandbox/settings.json
@$(CONTAINER_CLI) manifest rm $(SANDBOX_IMAGE) 2>/dev/null || true
$(CONTAINER_CLI) build --platform linux/amd64 --manifest $(SANDBOX_IMAGE) sandbox/
$(CONTAINER_CLI) build --platform linux/arm64 --manifest $(SANDBOX_IMAGE) sandbox/
@echo "Built: $(SANDBOX_IMAGE) (multi-arch)"

push-sandbox: sandbox
$(CONTAINER_CLI) manifest push $(SANDBOX_IMAGE)

## Cross-compile harness binary for the runner image
cli-runner:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -o build/runner/harness .
@echo "Built: build/runner/harness ($(VERSION))"

## Runner image (harness binary + openshell CLI)
runner: cli-runner build/runner/Dockerfile
$(CONTAINER_CLI) build --platform $(PLATFORM) -t $(RUNNER_IMAGE) build/runner/
@echo "Built: $(RUNNER_IMAGE)"

push-runner: runner
$(CONTAINER_CLI) push $(RUNNER_IMAGE)

## ── Lint targets ─────────────────────────────────────────────────────

## Run go vet
Expand All @@ -79,31 +62,31 @@ lint:
## CI mode auto-detects from the CI env var (set by GitHub Actions).
## Locally: full tests with credentials. On GHA: no-credential mode.

## Unit tests + bats + lint (fast, ~2min, no gateway needed)
## Vet + unit tests + bats (fast, ~2min, no gateway needed)
test: vet
CGO_ENABLED=0 go test ./...
bats test/preflight.bats

## Local gateway integration
test-local: cli test
## Local gateway integration (unit tests run separately via 'make test')
test-local: cli
./test/test-flow.sh local

## Kind: self-contained cluster lifecycle
## Builds sandbox image locally and pre-loads into kind (no registry push needed).
## Use KEEP=1 to keep the cluster after tests (for debugging).
test-kind: cli test
test-kind: cli
$(CONTAINER_CLI) build -t $(DEV_SANDBOX_IMAGE) sandbox/
@echo ""
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) ./test/kind-lifecycle.sh $(if $(KEEP),--keep)
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) CONTAINER_CLI=$(CONTAINER_CLI) ./test/kind-lifecycle.sh $(if $(KEEP),--keep)

## Remote (OCP): requires KUBECONFIG set
test-remote: cli test dev-sandbox dev-runner
test-remote: cli dev-sandbox dev-runner
@test -n "$${KUBECONFIG}" || { echo "ERROR: Set KUBECONFIG for OCP (e.g. export KUBECONFIG=infracluster/kubeconfig)"; exit 1; }
@echo ""
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) RUNNER_IMAGE=$(DEV_RUNNER_IMAGE) ./test/test-flow.sh ocp

## All: local + kind + remote
test-all: test-local test-kind test-remote
## All: unit + local + kind + remote
test-all: test test-local test-kind test-remote

## ── Dev image builds ─────────────────────────────────────────────────

Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ tty: false

### Prerequisites

- [OpenShell CLI](https://github.com/NVIDIA/OpenShell) (`brew install openshell && brew services start openshell` on macOS)
- [OpenShell CLI](https://github.com/NVIDIA/OpenShell) (`brew tap nvidia/openshell && brew install openshell && brew services start openshell` on macOS)
- Podman/Docker
- Go 1.23+ (only needed for building from source)

Expand Down Expand Up @@ -136,6 +136,7 @@ See the [OpenShell docs](https://github.com/NVIDIA/OpenShell) for the full secur
| `agents/providers/profiles/` | OpenShell provider profiles (imported to gateway on registration) |
| `providers.toml` | Provider catalog: required inputs and health checks per provider |
| `gateways/*/gateway.toml` | Deployment target config: `local/` (Podman), `kind/`, `ocp/` (OpenShift) |
| `openshell.toml` | Deployment-level overrides (enabled providers, inference model, chart version) |
| `sandbox/Dockerfile` | Sandbox image: OpenShell base + MCP servers + CLI tools |
| `build/runner/Dockerfile` | Runner image: harness binary for in-cluster sandbox creation |
| `sandbox/policy.yaml` | Network egress rules applied to sandboxes |
Expand Down Expand Up @@ -164,12 +165,26 @@ harness providers [--force]
harness preflight [--strict]
Validate local credentials and prerequisites.

harness status
Show gateway, provider, and sandbox status.

harness logs [NAME] [-f]
Stream sandbox logs (-f to follow).

harness stop [NAME] / harness start [NAME]
Stop or start a sandbox without deleting it.

harness teardown [--sandboxes] [--providers] [--k8s]
Tear down resources. At least one flag required.
```

## References
## Documentation Map

- [AGENTS.md](AGENTS.md) -- coding guidelines, project principles, workaround tracking
- [SPEC.md](SPEC.md) -- full CLI specification
- [TODO.md](TODO.md) -- roadmap and known gaps
| Document | What it is |
|----------|------------|
| [SPEC.md](SPEC.md) | **Authoritative** behavior spec for the CLI — commands, configs, payload |
| [AGENTS.md](AGENTS.md) | Contributor guide: coding principles, workaround tracking, validation modes |
| [TODO.md](TODO.md) | Roadmap and known gaps |
| [docs/archive/](docs/archive/README.md) | Historical design docs (e.g. the June 2026 design-v1 proposal) — outdated, kept for context |
| [docs/release-plan.md](docs/release-plan.md) | Release phases: CI (done), embed + `harness init`, GoReleaser |
| [docs/proto-migration.md](docs/proto-migration.md) | Deferred plan to adopt proto-generated config types |
20 changes: 19 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ Register providers with the gateway. Reads `providers.toml` for the catalog, imp

Validate local credentials and prerequisites against `providers.toml`.

### `harness status`

Show gateway, provider, and sandbox status. Read-only.

### `harness logs [NAME] [-f|--follow]`

Stream logs for a sandbox (name resolution delegated to `openshell sandbox logs` when NAME is omitted).

### `harness stop [NAME]` / `harness start [NAME]`

Stop or start a sandbox without deleting it. When NAME is omitted and exactly one sandbox is running, it is used; otherwise the command errors.

### `harness teardown [--sandboxes] [--providers] [--k8s]`

Tear down resources. At least one flag required.
Expand All @@ -106,7 +118,7 @@ In-cluster command for the runner Job. Reads agent config from `/etc/openshell/s

## Image Tags

All images are published to `ghcr.io/robbycochran/harness-openshell`. No floating tags (`:latest`, `:sandbox`, `:runner`) are used.
All images are published to `ghcr.io/robbycochran/harness-openshell`. CI never publishes floating tags (`:latest`, `:sandbox`, `:runner`); the bare `:sandbox` fallback below exists only for local `go build` binaries without version ldflags.

| Trigger | Sandbox | Runner |
|---------|---------|--------|
Expand All @@ -130,8 +142,14 @@ The CLI resolves images from its embedded version (set via `-ldflags` at build t
| `HARNESS_DIR` | Override harness directory detection |
| `OPENSHELL_NAMESPACE` | Override K8s namespace (default: `openshell`) |
| `OPENSHELL_CLI` | Override openshell binary path |
| `OPENSHELL_MODEL` | Inference model for provider registration (default: `claude-sonnet-4-6`) |
| `OPENSHELL_CHART_VERSION` | Override Helm chart version (beats `openshell.toml` and `gateway.toml`) |
| `PULL_SECRET` / `SANDBOX_PULL_SECRET` | Image pull secret names passed to the Helm install |
| `CONFIG_TOML` / `PROVIDERS_TOML` | Override paths to `openshell.toml` / `providers.toml` (preflight) |
| `KUBECONFIG` | K8s cluster config for remote targets |

`GATEWAY_ENDPOINT` and `GATEWAY_NAME` are internal — set on the in-cluster runner Job, not by users.

## Payload

The harness renders agent config into a self-contained payload uploaded to `/sandbox/.config/openshell/`:
Expand Down
39 changes: 28 additions & 11 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,29 @@
- Two sources of truth: gateway.toml hardcodes a registry, env vars override it
- Consider: gateway.toml uses a `registry` field and images are relative to it

### Consolidate internal/profile into internal/agent
- ✅ Removed dead code: `Parse()`, `ParseFile()`, `BuildSandboxEnv()`
- Only `Config` struct, `ValidateProviders`, and `StageHarnessDir` remain
- TODO: Move `ValidateProviders` to agent or gateway package, inline `Config` into sandbox.go
- TODO: Remove TOML dependency (github.com/BurntSushi/toml) if no longer used
### registerProviders should filter by agent's provider list
- `registerProviders()` in `cmd/providers.go` uses the gateway config's provider
list, not the agent config's. When `gwCfg` is nil (common case), it tries to
register all providers regardless of what the agent needs.
- Why: confusing output — users see "skipped" messages for providers their
agent doesn't reference. No functional impact (missing credentials are
silently handled).
- Fix: pass the agent's provider names to `registerProviders` and use them as
a filter alongside (or instead of) the gateway config's list.
- Files: `cmd/providers.go` (registerProviders signature), `cmd/up.go` (call site)

## Config Format

- [ ] Remove `providers.toml`; add provider profile validation in its place
- [ ] Convert gateway configs from TOML to YAML
- [ ] Specify/document the YAML formats (agent config, provider profiles)
- [ ] Document non-secret provider env vars (what `providers[].config` captures
and why it exists alongside secret credentials)

## CLI

- [ ] Flows that support agent.yaml (`create`, `up`) should also support
`--provider-profile` and provider config overrides

## Agent Config

Expand All @@ -31,15 +49,14 @@
## Testing

### Current coverage
- Go unit tests across cmd/, internal/agent, internal/gateway, internal/k8s
- 29 bats preflight tests
- Integration: local + kind + OCP via `make dev-test-all`
- Go unit tests across cmd/ (including launch.go) and all internal/ packages
- 29 bats preflight tests (run in CI via `.github/workflows/ci.yml`)
- Integration: local + kind + OCP via `make test-all`

### Gaps
- [ ] Tests for cmd/launch.go (configureGateway mTLS, in-cluster flow)
- [ ] Integration test for `providers --force`
- [ ] Preflight Go unit tests (internal/preflight/ has no _test.go)
- [ ] Add bats step to `.github/workflows/ci.yml` (Makefile ci runs bats, GHA doesn't)
- [ ] Unit test for the full `runLaunch` orchestration (currently only its
helpers — configureGateway, checkProviders, launchCreateSandbox — are tested)

## Release

Expand Down
Loading
Loading