GreenThreadDocs

The GreenThread engine is licenced. Enforcement has two tiers:

  • Unlicenced but within the grace period (the controller can't reach the licence server, but the cached JWT hasn't expired yet — or a fresh cluster hasn't validated for the first time). New work is blocked: the controller refuses to render new Model pods, the admission webhook denies new Model / GPUShareClaim resources, and the agent refuses to prepare new DRA claims. Already-running workloads keep serving. A transient network blip or a controller restart never kills live pods.
  • Expired (the cached JWT's 7-day exp has definitively passed). The controller actively tears running workloads down — it deletes the Model's pods and any pod consuming a GPUShareClaim (the libgreenthread.so GPU-sharing workloads), freeing the GPUs and emitting a LicenceExpired Kubernetes event on each. The Model / GPUShareClaim objects are left in place, so a restored licence rebuilds everything automatically.

How it works

  1. You install the chart with a licence token (gthread_xxx). The chart creates the greenthread-licence Secret in the operator namespace and writes the token to it.
  2. The leader-elected controller phones home to licence.greenthread.ai/api/v1/validate once an hour, posting the token plus light telemetry (GPU count, total VRAM).
  3. The server returns a signed JWT (Ed25519, 7-day expiry) that the controller writes back to the same Secret under cached-jwt.
  4. The webhook + agent each watch the Secret and verify the JWT offline against the licence server's public key (compiled into every binary at build time via -ldflags).
  5. If the controller can't reach the server, the existing JWT keeps working until its own exp — that gives you a built-in 7-day grace period against transient network failures.

What's gated

ComponentWhen unlicenced (grace)When expired
WebhookModel.Create and GPUShareClaim.Create are denied at admission with "greenthread cluster is unlicenced (<reason>) — Model creation denied".Same.
ControllerModel and GPUShareClaim reconciles set Licenced=False condition and skip pod / template rendering.Sets Licenced=False with reason Expired, deletes the Model's pods + conversion Jobs and every pod consuming a GPUShareClaim, and records a LicenceExpired Warning event.
Agent (DRA)NodePrepareResources returns a kubelet-visible error: "greenthread cluster is unlicenced (<reason>) — refusing to prepare claim …".Same — and there's nothing left to prepare, since the controller already deleted the pods.

Update and Delete are not gated — operators can still edit specs and tear down work after a lapse. During the grace window existing pods keep serving and only new work is blocked; once the licence is definitively expired the controller stops the running pods too (see below). Teardown keys on a genuinely past exp — never on a transient "can't reach the server" state — so an outage or a controller restart can't nuke live workloads.

Install

The licence is a top-level Helm value:

helm upgrade --install greenthread \
  oci://licence.greenthread.ai/greenthread/charts/greenthread \
  --namespace greenthread-system --create-namespace \
  --set licence.token=gthread_xxx \
  --set licence.serverURL=https://licence.greenthread.ai
ValueDefaultPurpose
licence.enabledtrueSet false to skip the Secret + RBAC and run the binaries in unlicenced mode (dev clusters only).
licence.token""Customer-provided token. Required when licence.enabled=truehelm install fails at template time if empty.
licence.serverURLhttps://licence.greenthread.aiLicence server base URL — the controller phones home here.

Pulling charts and images

licence.greenthread.ai doubles as an OCI registry. It authenticates with your licence token and proxies all reads to the upstream private repos. Every chart and image lives under the same licence.greenthread.ai/greenthread/<name> prefix:

URLProxies to
oci://licence.greenthread.ai/greenthread/charts/greenthreadThe engine Helm chart
oci://licence.greenthread.ai/greenthread/charts/liquidcomputeThe LiquidCompute Helm chart
oci://licence.greenthread.ai/greenthread/charts/ai-consoleThe AI Console Helm chart
licence.greenthread.ai/greenthread/greenthread-{agent,controller,webhook,sidecar}Engine container images
licence.greenthread.ai/greenthread/liquidcompute/{lcd,lc-proxy,frontend}LiquidCompute images
licence.greenthread.ai/greenthread/ai-console/{backend,frontend}AI Console images

Helm registry login

You only need this on the machine running helm install (it stores credentials in ~/.config/helm/registry/config.json):

helm registry login licence.greenthread.ai \
  --username licence \
  --password <your-licence-token>

The username is ignored by the licence server — anything works. The password is your licence token.

Image pull secret

Every pod that pulls a licence.greenthread.ai/... image needs an in-cluster docker-registry Secret. Create one per namespace where pods will run:

kubectl create namespace greenthread-system
kubectl create secret docker-registry greenthread-registry \
  -n greenthread-system \
  --docker-server=licence.greenthread.ai \
  --docker-username=licence \
  --docker-password=<your-licence-token>

Repeat the second command for liquidcompute-system and ai-console if you install those layers — or use a tool like reflector to mirror it across namespaces.

Reference the Secret in each chart install via --set 'global.imagePullSecrets[0].name=greenthread-registry' (or the chart's equivalent — imagePullSecrets[0].name on AI Console).

The Secret is annotated with helm.sh/resource-policy: keep and templated with a lookup, so the cached-jwt value the controller writes back survives helm upgrade — you won't lose the cache between rollouts.

Verify

# Token + cached JWT both present after the first /api/v1/validate (~ within 1 min of install).
$ kubectl get secret greenthread-licence -n greenthread-system -o json \
    | jq '.data | keys'
[
  "cached-jwt",
  "token"
]

# Last successful check-in.
$ kubectl get secret greenthread-licence -n greenthread-system \
    -o jsonpath='{.metadata.annotations.greenthread\.ai/last-check-in}'
2026-05-20T03:48:16Z

# Controller logs confirm validation.
$ kubectl logs -n greenthread-system -l app.kubernetes.io/component=controller \
    --tail=50 | grep licence

If cached-jwt doesn't appear within ~1 minute, check the controller logs for the validate failure — usually a wrong token or unreachable server.

What happens when the licence expires

  1. The controller fails to refresh and logs the failure each hour.
  2. The cached JWT keeps the cluster fully working for up to 7 days (the server-issued exp) — including all running workloads. This is the grace period.
  3. Once the cached JWT's exp passes, the cluster is expired. On its next reconcile (within ~1 minute) the controller deletes every Model pod, conversion Job, and GPUShareClaim-consuming pod, freeing the GPUs and recording a LicenceExpired event on each affected object. Serving stops. New work stays blocked at the webhook + DRA as before.
  4. Nothing is permanently destroyed — the Model / GPUShareClaim objects remain. Restore the licence (fix connectivity, or get a fresh token) and the controller rebuilds the pods on the next successful /api/v1/validate, with no manual re-apply.

You can watch a teardown with kubectl get events -n <namespace> --field-selector reason=LicenceExpired.