Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a990268
Add community repo structure, sandbox images, and CI workflow
drew Mar 3, 2026
4aba881
Add skills directory
drew Mar 3, 2026
ddad93a
Fix sandbox detection: use if/then to avoid non-zero exit under pipefail
drew Mar 3, 2026
effc140
Move skills inside sandboxes, remove top-level skills directory
drew Mar 3, 2026
f691075
Link SECURITY.md properly in README
drew Mar 3, 2026
5e79608
Remove cosmos sandbox for now
drew Mar 4, 2026
728e719
Rename --image to --from in all sandbox examples
drew Mar 4, 2026
83286e4
Add sandbox policy to openclaw image
drew Mar 4, 2026
ecf9335
Make openclaw Dockerfile self-contained (no external base image)
drew Mar 4, 2026
b81b299
Publish sandbox images under sandboxes/ path in GHCR
drew Mar 4, 2026
fdc0ed9
Only tag sandbox images with sha and latest
drew Mar 4, 2026
77efee6
wip
drew Mar 4, 2026
420bb4b
Add multi-platform builds (amd64 + arm64) via QEMU
drew Mar 4, 2026
8e6a866
Only push images on main branch
drew Mar 4, 2026
fd8ea25
Replace brev/TODO.md with .gitkeep
drew Mar 4, 2026
684d8ad
Add DCO sign-off check workflow
drew Mar 4, 2026
72770d7
Add SPDX license header check workflow and script
drew Mar 4, 2026
1b76339
Add base sandbox image, refactor openclaw to build from it
drew Mar 4, 2026
7091bd4
Add ONBUILD COPY for policy.yaml in base sandbox
drew Mar 4, 2026
35ae0e4
Revert ONBUILD: explicitly COPY policy.yaml in child images
drew Mar 4, 2026
5796e7f
Use ubuntu:24.04 as base image instead of python:3.12-slim
drew Mar 4, 2026
f5c26d1
Only create .agents/skills in base sandbox
drew Mar 4, 2026
868372f
Simplify Dockerfile section comments
drew Mar 4, 2026
f08404d
Fix sandbox image builds: lowercase repository name in image references
drew Mar 4, 2026
332b592
Fix PR builds: use local registry for base image in dependent sandbox…
drew Mar 4, 2026
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
219 changes: 219 additions & 0 deletions .github/workflows/build-sandboxes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: Build Sandbox Images

on:
push:
branches: [main]
paths:
- "sandboxes/**"
pull_request:
branches: [main]
paths:
- "sandboxes/**"
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository }}

permissions:
contents: read
packages: write

jobs:
# ---------------------------------------------------------------------------
# Detect which sandbox images have changed
# ---------------------------------------------------------------------------
detect-changes:
name: Detect changed sandboxes
runs-on: ubuntu-latest
outputs:
base-changed: ${{ steps.changes.outputs.base_changed }}
sandboxes: ${{ steps.changes.outputs.sandboxes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Determine changed sandboxes
id: changes
run: |
set -euo pipefail

if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# Build everything on manual trigger
BASE_CHANGED="true"
SANDBOXES=$(find sandboxes -mindepth 1 -maxdepth 1 -type d -name '*.base' -prune -o -exec test -f {}/Dockerfile \; -print \
| xargs -I{} basename {} \
| grep -v '^base$' \
| jq -R -s -c 'split("\n") | map(select(length > 0))')
else
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
else
BASE_SHA="${{ github.event.before }}"
fi

CHANGED=$(git diff --name-only "$BASE_SHA" "${{ github.sha }}" -- sandboxes/)

# Check if base changed
if echo "$CHANGED" | grep -q '^sandboxes/base/'; then
BASE_CHANGED="true"
# When base changes, rebuild all sandboxes that have a Dockerfile
SANDBOXES=$(find sandboxes -mindepth 1 -maxdepth 1 -type d -exec test -f {}/Dockerfile \; -print \
| xargs -I{} basename {} \
| grep -v '^base$' \
| jq -R -s -c 'split("\n") | map(select(length > 0))')
else
BASE_CHANGED="false"
SANDBOXES=$(echo "$CHANGED" \
| cut -d'/' -f2 \
| sort -u \
| while read -r name; do
if [ "$name" != "base" ] && [ -f "sandboxes/${name}/Dockerfile" ]; then echo "$name"; fi
done \
| jq -R -s -c 'split("\n") | map(select(length > 0))')
fi
fi

echo "base_changed=${BASE_CHANGED}" >> "$GITHUB_OUTPUT"
echo "sandboxes=${SANDBOXES}" >> "$GITHUB_OUTPUT"
echo "Base changed: ${BASE_CHANGED}"
echo "Will build: ${SANDBOXES}"

# ---------------------------------------------------------------------------
# Build the base sandbox image (other sandboxes depend on this)
# ---------------------------------------------------------------------------
build-base:
name: Build base
needs: detect-changes
if: needs.detect-changes.outputs.base-changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Lowercase image prefix
id: repo
run: echo "image_prefix=${IMAGE_PREFIX,,}" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/base
tags: |
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: sandboxes/base
platforms: linux/amd64,linux/arm64
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=base
cache-to: type=gha,mode=max,scope=base

# ---------------------------------------------------------------------------
# Build dependent sandbox images (after base completes)
# ---------------------------------------------------------------------------
build:
name: Build ${{ matrix.sandbox }}
needs: [detect-changes, build-base]
if: |
always() &&
needs.detect-changes.result == 'success' &&
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped') &&
needs.detect-changes.outputs.sandboxes != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sandbox: ${{ fromJson(needs.detect-changes.outputs.sandboxes) }}
steps:
- uses: actions/checkout@v4

- name: Lowercase image prefix
id: repo
run: echo "image_prefix=${IMAGE_PREFIX,,}" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

# On PRs, use a local registry so the base image built in this job is
# accessible to the subsequent buildx build (docker-container driver
# cannot see images loaded into the host daemon).
- name: Start local registry (PR only)
if: github.ref != 'refs/heads/main'
run: docker run -d -p 5000:5000 --name registry registry:2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: ${{ github.ref != 'refs/heads/main' && 'network=host' || '' }}

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# On PRs the base image is not in GHCR. Build it locally, push to the
# local registry, and override BASE_IMAGE to point there.
- name: Build base image locally (PR only)
if: github.ref != 'refs/heads/main'
uses: docker/build-push-action@v6
with:
context: sandboxes/base
push: true
tags: localhost:5000/sandboxes/base:latest
cache-from: type=gha,scope=base

- name: Set BASE_IMAGE
id: base
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "image=${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/base:latest" >> "$GITHUB_OUTPUT"
else
echo "image=localhost:5000/sandboxes/base:latest" >> "$GITHUB_OUTPUT"
fi

- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.repo.outputs.image_prefix }}/sandboxes/${{ matrix.sandbox }}
tags: |
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: sandboxes/${{ matrix.sandbox }}
platforms: ${{ github.ref == 'refs/heads/main' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BASE_IMAGE=${{ steps.base.outputs.image }}
cache-from: type=gha,scope=${{ matrix.sandbox }}
cache-to: type=gha,mode=max,scope=${{ matrix.sandbox }}
17 changes: 17 additions & 0 deletions .github/workflows/dco.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: DCO

on:
pull_request:
branches: [main]

jobs:
dco:
name: DCO Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Check DCO sign-off
uses: gsactions/dco-check@v1.1.1
27 changes: 27 additions & 0 deletions .github/workflows/license-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: License Headers

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
license-headers:
name: Check License Headers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Check SPDX license headers
run: python scripts/check_license_headers.py --check
72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing to NemoClaw Community

Thank you for your interest in contributing to the NemoClaw Community ecosystem. This guide covers everything you need to get started.

## Ways to Contribute

- **Sandbox images** -- Add new domain-specific sandbox environments under `sandboxes/`
- **Skills** -- Create agent skills and tool definitions inside a sandbox's `skills/` directory
- **Bug fixes** -- Fix issues in existing sandboxes, skills, or configurations
- **Documentation** -- Improve READMEs, guides, and usage examples
- **Integrations** -- Connect NemoClaw to new tools, platforms, or workflows

## Getting Started

1. Fork this repository
2. Clone your fork locally
3. Create a feature branch from `main`

```bash
git clone https://github.com/<your-username>/NemoClaw-Community.git
cd NemoClaw-Community
git checkout -b my-feature
```

## Adding a Sandbox Image

Each sandbox lives in its own directory under `sandboxes/`:

```
sandboxes/my-sandbox/
Dockerfile
README.md
...
```

Requirements:
- A `Dockerfile` that builds cleanly
- A `README.md` describing the sandbox's purpose, usage, and any prerequisites
- Keep images minimal -- only include what's needed for the workload

## Adding a Skill

Skills live inside their sandbox's `skills/` directory (e.g., `sandboxes/openclaw/skills/my-skill/`). Each skill should include:
- A `SKILL.md` describing what it does and when to use it
- Any supporting files the skill needs
- A README with usage examples

## Submitting a Pull Request

1. Ensure your changes are focused -- one feature or fix per PR
2. Include a clear description of what your PR does and why
3. Test your changes locally before submitting
4. Update any relevant documentation

## Development Guidelines

- Follow existing naming conventions and directory structures
- Write clear commit messages
- Keep PRs small and reviewable
- Respond to review feedback promptly

## Reporting Issues

Use GitHub Issues for bug reports and feature requests. Include:
- A clear title and description
- Steps to reproduce (for bugs)
- Expected vs. actual behavior
- Environment details (OS, Docker version, GPU, etc.)

## License

By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.
Loading