Workspace Spawner & Annotations Guide
The Spawner subsystem in @nogoo9/no-crd is designed for agentic sandboxing. It allows AI agents and API clients to provision, list, and terminate isolated containerized workspaces (pods) on-demand using standard Kubernetes APIs without Custom Resource Definitions.
This guide covers the spawner architecture, tool configurations, supported lifecycle annotations, and concrete deployment examples.
🏗️ Spawner Architecture
When an agent invokes spawn_workspace, the spawner processes the request, loads the referenced template ConfigMap, applies annotations, and injects runtime configurations before committing the pod to the Kubernetes API.
🏷️ Supported Annotations
Pod templates and inline specifications can declare special annotations that direct the Spawner to inject extra features, security configurations, and lifecycle hooks into the spawned workspace pod:
| Annotation Key | Type | Description |
|---|---|---|
nogoo9/required-context | string (comma-separated) | Validates that target environment variables are provided in the tool call's context parameter (e.g. GITHUB_TOKEN,DATABASE_URL). |
nogoo9/iam-role-arn | string | Instructs the spawner to provision a dedicated ServiceAccount bound to this AWS/GCP IAM Role ARN and assign it to the pod. |
nogoo9/init-image | string | The container image to use for a spawned init-container (e.g. alpine, git). |
nogoo9/init-command | string | The shell command to run in the init-container. It automatically shares the main container's volume mounts. |
nogoo9/pre-stop-command | string | A shell command to execute in the workspace before termination (e.g., to commit state or push logs). |
nogoo9/pre-stop-sidecar-image | string | If specified, runs the pre-stop-command inside a dedicated sidecar container instead of the main workspace container. |
nogoo9/default-grace-period | number | The termination grace period in seconds to assign to the pod (defaults to 60 if a pre-stop command is present). |
🔄 Pod Injection Lifecycle
The diagram below shows the order in which init-containers, main containers, and pre-stop lifecycle hooks execute within a spawned workspace:
📖 Practical Example
Here is a complete end-to-end walk-through of declaring a template, spawning a workspace using the MCP tool, and reviewing the generated Kubernetes manifest.
1. Template ConfigMap (dev-environment-template.yaml)
This ConfigMap defines a basic node workspace template and sets annotations to mandate environment keys, use an Alpine container to clone a repository, and hook up a git push cleanup command:
apiVersion: v1
kind: ConfigMap
metadata:
name: dev-node-template
namespace: nogoo9
labels:
nogoo9/pod-template: "true"
annotations:
nogoo9/required-context: "GITHUB_TOKEN,GIT_REPO_URL"
nogoo9/init-image: "alpine/git:latest"
nogoo9/init-command: "git clone $GIT_REPO_URL /workspace"
nogoo9/pre-stop-command: "cd /workspace && git add -A && git commit -m 'save state' && git push"
nogoo9/default-grace-period: "120"
data:
spec: |
{
"containers": [
{
"name": "workspace",
"image": "node:22-alpine",
"command": ["sleep", "infinity"],
"volumeMounts": [
{
"name": "code-volume",
"mountPath": "/workspace"
}
]
}
],
"volumes": [
{
"name": "code-volume",
"emptyDir": {}
}
]
}2. MCP Tool Call (spawn_workspace)
The AI agent invokes spawn_workspace using the template reference and satisfies the context requirements:
{
"id": "agent-session-45",
"templateRef": "dev-node-template",
"namespace": "nogoo9",
"context": {
"GITHUB_TOKEN": "ghp_1234567890abcdef",
"GIT_REPO_URL": "https://github.com/myorg/workspace-project.git"
}
}3. Generated Kubernetes Pod Manifest (Result)
The Spawner processes the parameters and submits the following Pod to the Kubernetes API:
apiVersion: v1
kind: Pod
metadata:
name: ws-anonymous-agent-session-45
namespace: nogoo9
labels:
nogoo9/type: "workspace"
nogoo9/workspace-id: "agent-session-45"
nogoo9/managed-by: "nogoo9-spawner"
nogoo9/user-sub: "anonymous"
spec:
terminationGracePeriodSeconds: 120
initContainers:
- name: spawner-init
image: alpine/git:latest
command: ["/bin/sh", "-c", "git clone $GIT_REPO_URL /workspace"]
volumeMounts:
- name: code-volume
mountPath: "/workspace"
env:
- name: GITHUB_TOKEN
value: "ghp_1234567890abcdef"
- name: GIT_REPO_URL
value: "https://github.com/myorg/workspace-project.git"
containers:
- name: workspace
image: node:22-alpine
command: ["sleep", "infinity"]
volumeMounts:
- name: code-volume
mountPath: "/workspace"
env:
- name: GITHUB_TOKEN
value: "ghp_1234567890abcdef"
- name: GIT_REPO_URL
value: "https://github.com/myorg/workspace-project.git"
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "cd /workspace && git add -A && git commit -m 'save state' && git push"]
volumes:
- name: code-volume
emptyDir: {}🌐 Workspace Routing Proxy (Experimental)
(Available from v0.2.0 - Experimental)
WARNING
The workspace routing proxy is experimental and likely to change in the next version.
The server includes a built-in reverse proxy routing service. This enables clients (and AI agents) to access web services running inside workspace containers (e.g. IDE servers, Jupyter notebooks, mock servers) without creating Kubernetes Services or Ingress rules for every container.
How it Works
When a client requests a URL matching: http://<gateway>/route/<workspaceId>/<subpath>
The server performs the following steps:
- Identity & JWT Resolution: Resolves the JWT token from the
Authorization: Bearer <token>header or?token=<token>query string. - Access Verification: Checks if
AUTH_ENABLEDis true. If yes, it verifies that thesub(or configured subject claim) in the JWT token matches the pod'snogoo9/user-sublabel. If it doesn't match, it returns a403 Forbidden. - Target Selection: Finds the corresponding workspace pod IP inside the cluster. It extracts the target port from the pod's
nogoo9/workspace-portannotation (falling back toDEFAULT_WORKSPACE_PORTor3000). - Streaming Proxy: Performs an HTTP fetch to
http://<pod-ip>:<port>/<subpath>and pipes request/response streams (headers, body, query parameters) back and forth.
Configuration
You can configure the behavior of the routing proxy using the following settings:
| Environment Variable | Description | Default |
|---|---|---|
DEFAULT_WORKSPACE_PORT | The default container port to which the proxy forwards traffic if no template annotation is defined. | 3000 |
BASE_URL | Subpath prefix for all server routes. If configured (e.g. /proxy), the routing proxy path shifts to /proxy/route/<workspaceId>/<subpath>. | (None) |
AUTH_ENABLED | If set to true, enforces JWT signature verification and checks that the user's identity matches the pod's nogoo9/user-sub label. | false |
Customizing Ports via Template Annotations
For workspace templates running services on non-standard ports (such as 8888 for Jupyter notebooks or 8080 for code-server), define the nogoo9/workspace-port annotation on the template ConfigMap. The proxy service reads this annotation on startup/lookup to route traffic to the correct container port.
Code Example: Workspace Pod with Target Port Annotation
Create a template specifying a custom workspace target port:
apiVersion: v1
kind: ConfigMap
metadata:
name: jupyter-workspace-template
namespace: nogoo9
labels:
nogoo9/pod-template: "true"
annotations:
nogoo9/description: "Jupyter Notebook environment"
nogoo9/workspace-port: "8888" # Proxy forwards requests to port 8888 inside the container
data:
spec: |
{
"containers": [
{
"name": "jupyter",
"image": "jupyter/base-notebook:latest",
"command": ["start-notebook.sh", "--NotebookApp.token=''"],
"ports": [
{ "containerPort": 8888 }
]
}
]
}When this workspace is spawned with ID session-a, you can connect directly to the Jupyter notebook via the gateway: http://localhost:3000/route/session-a/?token=YOUR_JWT_HERE
🔄 Lifecycle Hooks Examples
(Available from v0.2.0)
Provide hooks to pull state before starting workspaces and to backup/flush state before workspaces terminate.
1. State Setup (Git example)
Configure an init-container to pull code from Git at workspace startup:
annotations:
nogoo9/required-context: "GIT_REPO_URL"
nogoo9/init-image: "alpine/git:latest"
nogoo9/init-command: "git clone $GIT_REPO_URL /workspace"2. State Backup (S3 example)
Configure a preStop termination hook to backup data to MinIO/S3 on container shutdown:
annotations:
nogoo9/required-context: "AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,S3_BUCKET,S3_FOLDER"
nogoo9/pre-stop-command: "aws s3 sync /workspace s3://$S3_BUCKET/$S3_FOLDER --endpoint-url http://minio-service:9000"
nogoo9/pre-stop-sidecar-image: "amazon/aws-cli:latest"
nogoo9/default-grace-period: "120"