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
60 changes: 60 additions & 0 deletions agents/langgraph/templates/react_agent/Containerfile.openshell
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# LangGraph ReAct agent for NVIDIA OpenShell sandbox
#
# BYOC (Bring Your Own Container) image following the OpenShell pattern:
# https://github.com/NVIDIA/OpenShell/tree/main/examples/bring-your-own-container
#
# Build (copy repo-level images/ and components/auth/ into context first):
# cp -r ../../../../images ./images
# mkdir -p ./components && cp -r ../../../../components/auth ./components/auth
# trap 'rm -rf ./images ./components' EXIT
# podman build --platform linux/amd64 -t langgraph-sandbox:latest -f Containerfile.openshell .
#
# Run (OpenShell replaces CMD -- pass the start command after --):
# openshell sandbox create --from langgraph-sandbox:latest --forward 8080 \
# -e API_KEY=<key> -e BASE_URL=<url> -e MODEL_ID=<model> \
# -- uvicorn main:app --host 0.0.0.0 --port 8080

FROM registry.access.redhat.com/ubi9/python-312@sha256:e95978812895b9abb2bdc109b501078da2a47c8dbb9fa23758af40ed50ab6023

USER 0

# iproute: required by OpenShell for network namespace management
# nftables: optional, enables bypass detection (log + reject for direct connections)
RUN dnf install -y --nodocs iproute nftables && dnf clean all && rm -rf /var/cache/dnf

# uv for fast dependency installation
COPY --from=ghcr.io/astral-sh/uv@sha256:fc93e9ecd7218e9ec8fba117af89348eef8fd2463c50c13347478769aaedd0ce /uv /usr/local/bin/uv

# Application directory (OpenShell convention: /sandbox)
RUN install -d -o 1001 -g 0 -m 775 /sandbox
WORKDIR /sandbox

USER 1001

# Install the auth component first (resolves the path dependency issue),
# then install the main package with tracing extra.
COPY --chown=1001:0 components/auth/ ./components/auth/
RUN uv pip install --python /opt/app-root/bin/python3 --no-cache ./components/auth/

COPY --chown=1001:0 pyproject.toml .
COPY --chown=1001:0 src/ ./src/

# Remove the uv path source for agent-auth (already installed above from ./components/auth/)
# so uv doesn't try to resolve the relative path ../../../../components/auth from /sandbox.
RUN sed -i '/^\[tool\.uv\.sources\]/,/^\[/{/agent-auth/d}' pyproject.toml && \
uv pip install --python /opt/app-root/bin/python3 --no-cache ".[tracing]"

# Application code and assets
COPY --chown=1001:0 main.py .
COPY --chown=1001:0 playground/ ./playground/
COPY --chown=1001:0 images/ ./images/

ENV PORT=8080 \
PYTHONPATH=/sandbox \
PATH="/opt/app-root/bin:${PATH}"

EXPOSE 8080

# NOTE: OpenShell's sandbox supervisor replaces CMD at runtime.
# Pass the start command explicitly on the CLI after --.
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
109 changes: 109 additions & 0 deletions agents/langgraph/templates/react_agent/OPENSHELL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# LangGraph ReAct Agent — OpenShell Sandbox Deployment

Run the LangGraph ReAct agent inside an [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell) sandbox with policy-enforced network isolation and filesystem constraints.

This follows the [Bring Your Own Container](https://github.com/NVIDIA/OpenShell/tree/main/examples/bring-your-own-container) pattern: a standard Linux container image with no OpenShell-specific dependencies.

## Prerequisites

- [Podman](https://podman.io/) or Docker installed
- [OpenShell CLI](https://github.com/NVIDIA/OpenShell) installed and connected to a gateway
- An OpenAI-compatible inference endpoint reachable from the sandbox (vLLM, OGX, Llama Stack, or remote API)

## Build

```bash
cd agents/langgraph/templates/react_agent

# Copy repo-level assets into build context
cp -r ../../../../images ./images
mkdir -p ./components && cp -r ../../../../components/auth ./components/auth

podman build --platform linux/amd64 -t quay.io/<your-org>/langgraph-sandbox:latest -f Containerfile.openshell .
podman push quay.io/<your-org>/langgraph-sandbox:latest

# Clean up build context
rm -rf ./images ./components
```

Ensure the quay.io repository is public so the cluster can pull without imagePullSecrets.

## Run

OpenShell's sandbox supervisor replaces the image's CMD/ENTRYPOINT at runtime. You **must** pass the application start command explicitly after `--`:

```bash
openshell sandbox create \
--name langgraph-agent \
--from quay.io/<your-org>/langgraph-sandbox:latest \
--forward 8080 \
-e API_KEY=not-needed-for-local \
-e BASE_URL=http://vllm-svc.my-ns.svc.cluster.local:8000/v1 \
-e MODEL_ID=llama3.1:8b \
-- uvicorn main:app --host 0.0.0.0 --port 8080
```

Flags:

- `--forward 8080` opens an SSH tunnel so `localhost:8080` on your machine reaches the agent inside the sandbox
- `-e` injects environment variables via OpenShell providers (never written to disk)
- `-- <command>` is the process the supervisor executes via SSH once the sandbox is ready

## Verify

```bash
# Health check (should return {"status": "healthy", "agent_initialized": true})
curl -s http://localhost:8080/health | python3 -m json.tool

# Chat completion
curl -s -X POST http://localhost:8080/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"What is OpenShift?"}],"stream":false}' \
| python3 -m json.tool
```

## Network Policy

The ReAct agent only needs outbound access to the configured LLM endpoint. Apply a restrictive policy:

```yaml
sandbox:
network:
egress:
- host: "vllm-svc.my-ns.svc.cluster.local"
port: 8000
methods: ["POST"]
paths:
- "/v1/chat/completions"
- "/v1/completions"
```

```bash
openshell policy set langgraph-agent --policy policy.yaml --wait
```

## Cleanup

```bash
openshell sandbox delete langgraph-agent
```

## How It Works

OpenShell isolates the sandbox container and routes all outbound traffic through its policy engine. The LangGraph agent runs as a normal FastAPI/uvicorn process inside the container — no code changes required. The key differences from a standard Helm deployment:

| Aspect | Standard (Helm) | OpenShell Sandbox |
|--------|----------------|-------------------|
| Base image | UBI9 Python 3.12 | Same (UBI9 Python 3.12) |
| Network isolation | K8s NetworkPolicy | OpenShell L7 policy engine |
| Credential injection | Helm secrets / env vars | OpenShell providers (`-e` flags) |
| Process supervision | Container runtime PID 1 | OpenShell supervisor via SSH |
| Start command | Dockerfile CMD | Explicit `-- <command>` |

## Notes

- The ReAct agent is a long-running FastAPI server. Unlike CLI agents (Claude Code, Codex), it does not need interactive terminal access.
- `BASE_URL` must be reachable from within the sandbox. Use cluster-internal DNS for in-cluster endpoints.
- If the model endpoint is unreachable, `/health` still returns healthy but `/chat/completions` will fail with a 500.
- Build with `--platform linux/amd64` when targeting x86_64 clusters from Apple Silicon machines.
- The `components/auth` dependency is pre-installed in the image for token-based auth support.
Loading