Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
host-side-api-server/servers/repo-provisioner/__pycache__/
host-side-api-server/bin/
4 changes: 4 additions & 0 deletions host-side-api-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
policies/rendered-*.yaml
bin/
__pycache__/
*.pyc
139 changes: 139 additions & 0 deletions host-side-api-server/HOW_TO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
## Purpose

Run the host-side API server experiment to validate that agents inside an
OpenShell sandbox can call API servers running on the host through the L7
proxy.

## Requirements

| Requirement | Link |
|-------------|------|
| Go 1.23+ | https://go.dev/dl/ |
| Python 3.11+ | https://www.python.org/downloads/ |
| OpenShell CLI v0.0.43+ | https://github.com/NVIDIA/OpenShell |
| openshell-gateway v0.0.43+ | https://github.com/NVIDIA/OpenShell/releases |
| Podman (rootless) | https://podman.io/docs/installation |
| curl | (pre-installed on most systems) |
| git | https://git-scm.com/downloads |

### OpenShell setup

This experiment uses the standalone `openshell-gateway` binary with the
Podman driver — not the older K3s-in-Docker approach (`openshell gateway
start`, removed in v0.0.37).

1. Install Podman and start the socket:
```bash
systemctl --user start podman.socket
```

2. Download the `openshell-gateway` binary from the
[OpenShell releases](https://github.com/NVIDIA/OpenShell/releases)
page and place it in your `$PATH`.

3. Pull the required images:
```bash
podman pull ghcr.io/nvidia/openshell/supervisor:latest
podman pull ghcr.io/nvidia/openshell-community/sandboxes/base:latest
```

4. Start the gateway:
```bash
OPENSHELL_SSH_HANDSHAKE_SECRET="$(python3 -c 'import secrets; print(secrets.token_hex(16))')" \
OPENSHELL_SUPERVISOR_IMAGE="ghcr.io/nvidia/openshell/supervisor:latest" \
OPENSHELL_SANDBOX_IMAGE="ghcr.io/nvidia/openshell-community/sandboxes/base:latest" \
OPENSHELL_SANDBOX_IMAGE_PULL_POLICY="missing" \
openshell-gateway \
--bind-address 0.0.0.0 \
--port 18080 \
--health-port 18081 \
--drivers podman \
--disable-tls \
--db-url "sqlite:/tmp/openshell-gateway.db?mode=rwc" \
--log-level info &
```

5. Register the gateway with the CLI:
```bash
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/openshell"
mkdir -p "${CONFIG_DIR}/gateways/podman-local"
cat > "${CONFIG_DIR}/gateways/podman-local/metadata.json" <<'EOF'
{
"name": "podman-local",
"gateway_endpoint": "http://127.0.0.1:18080",
"is_remote": false,
"gateway_port": 18080,
"auth_mode": "plaintext"
}
EOF
printf 'podman-local' > "${CONFIG_DIR}/active_gateway"
```

6. Verify the gateway is healthy:
```bash
curl -sf http://127.0.0.1:18081/healthz
```

### Environment variables

| Variable | Description |
|----------|-------------|
| `FULLSEND_GCP_PROJECT_ID` | GCP project with Vertex AI API enabled |
| `CLOUD_ML_REGION` | Vertex AI region (default: `global`) |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to a service account key JSON with `roles/aiplatform.user` in the project |

Create a service account and key if you don't have one:

```bash
gcloud iam service-accounts create fullsend-runner \
--display-name="Fullsend Runner" \
--project=<your-gcp-project>

gcloud projects add-iam-policy-binding <your-gcp-project> \
--member="serviceAccount:fullsend-runner@<your-gcp-project>.iam.gserviceaccount.com" \
--role="roles/aiplatform.user" \
--condition=None

gcloud iam service-accounts keys create /tmp/sa-key.json \
--iam-account=fullsend-runner@<your-gcp-project>.iam.gserviceaccount.com

export GOOGLE_APPLICATION_CREDENTIALS=/tmp/sa-key.json
export FULLSEND_GCP_PROJECT_ID=<your-gcp-project>
```

The `fullsend run` CLI must also be installed and in your `$PATH`.

## Steps

### Automated (fullsend run)

1. Navigate to the experiment directory:
```bash
cd experiments/host-side-api-server
```

2. Run the setup script to build Go binaries and verify prerequisites:
```bash
./setup.sh
```

3. Run a harness:
```bash
./run.sh baked-instructions-full
```

Available harnesses (3 discovery methods × 2 policies):
- `baked-instructions-full` / `baked-instructions-restricted`
- `openapi-discovery-full` / `openapi-discovery-restricted`
- `tooluse-discovery-full` / `tooluse-discovery-restricted`

4. Results are saved to `results/`.

## Expected Output

- Both API servers start and pass health checks
- Sandbox is created with L7 policy applied
- From inside the sandbox, `curl` to API server endpoints succeeds for
allowed endpoints and returns 403 for restricted ones
- Container build via host API completes successfully
- Repo provisioning clones, scans, and reports results
37 changes: 37 additions & 0 deletions host-side-api-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Experiment: Host-Side API Server for Sandboxed Agents

Tracking issue: [fullsend-ai/experiments#25](https://github.com/fullsend-ai/experiments/issues/25)

## What this experiment covers

1. **Basic API server lifecycle** — two API servers started by the
orchestrator, callable from inside an OpenShell sandbox via the L7 proxy
2. **Credential isolation** — servers hold credentials internally, agents
never see them
3. **Container build delegation** — Go server builds images via podman
on the host, working around OpenShell's seccomp restrictions
4. **API discoverability** — three approaches compared: OpenAPI spec,
tool-use schema, baked-in agent instructions
5. **Per-run auth** — UUID bearer token generated per run
6. **Long-running operations** — container builds that exceed MCP timeout
7. **L7 policy tuning** — most restrictive policy that allows the API

## Architecture

See [design spec](superpowers/2026-05-14-host-side-api-server-design.md).

## Quick start

See [HOW_TO.md](HOW_TO.md).

## Key design decisions

- **Two servers in different languages** (Go + Python) to validate the
language-agnostic process contract
- **Uniform process contract**: `--port`, `--token`, `/healthz`, SIGTERM
- **Repo provisioner depends on OpenShell#1272**: if content inspection hooks
ship in OpenShell, the scan-before-copy flow could be handled natively

## Findings

See [results/findings.md](results/findings.md) (populated after running).
46 changes: 46 additions & 0 deletions host-side-api-server/agents/test-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: test-agent
description: Tests host-side API servers by cloning a repo and building a container image.
tools: Bash(curl), Skill
model: sonnet
---

# Host-Side API Server Test Agent

You are inside an OpenShell sandbox. Two API servers are running on the host
and accessible via curl through the network proxy.

## Environment variables

- `$API_TOKEN` — bearer token for authenticating with both servers
- `$BUILDER_URL` — base URL of the container builder server
- `$PROVISIONER_URL` — base URL of the repo provisioner server
- Your sandbox name can be obtained with `hostname | sed 's/^sandbox-//'` — pass this as the `sandbox` field in API requests so the servers can upload/download files to your sandbox

## Authentication

All API requests require a bearer token:

```bash
curl -H "Authorization: Bearer $API_TOKEN" ...
```

The actual credential is managed by the network proxy — the token value
in your environment is a placeholder that the proxy resolves transparently.

## Your task

Use the skills available to you to learn how to call each API, then:

1. **Clone a repository**: Use the provisioner API to clone `fullsend-ai/fullsend`
(ref: `main`) into the sandbox at `/sandbox/fullsend`.
2. **Build a container image**: Use the builder API to build the image from the
Containerfile at `images/sandbox/Containerfile` in the cloned repo, with
tag `fullsend-sandbox:test`. Set `dest` to `/sandbox/fullsend-sandbox.tar`
so the built image tarball is uploaded back into the sandbox.

If any endpoint returns a 403, note it — the network policy may not allow
that endpoint. This is expected behavior, not an error. Report what worked
and what didn't.

Write your results to `/tmp/workspace/output/results.md` (create the directory first with `mkdir -p /tmp/workspace/output`).
4 changes: 4 additions & 0 deletions host-side-api-server/env/gcp-vertex.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export CLAUDE_CODE_USE_VERTEX=1
export ANTHROPIC_VERTEX_PROJECT_ID=${FULLSEND_GCP_PROJECT_ID}
export CLOUD_ML_REGION=${CLOUD_ML_REGION:-global}
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/workspace/.gcp-credentials.json
Loading
Loading