Single Helm chart repository for all AgentWorkforce services. Each service lives under charts/<service-name>/.
| Chart | Description |
|---|---|
| relaycast | Portable self-hosted Relaycast core — realtime agent communication with single-writer SQLite persistence |
| relayfile | Relayfile server — single Go binary, HTTP on :8080, Postgres-backed in production |
| relayflows | Durable workflows running locally in Kubernetes, with optional Cloud assignment mode |
| 96bulls-worker | 96Bulls sessions pipeline: HoopDb migrations, Locker bucket init, and the indexer/referee/bob-costas stage workers |
| 96bulls | The whole 96Bulls pipeline in one release: 96bulls-worker + alleyoop, with optional HoopDb (CloudNativePG), Locker (MinIO) and an OTel collector |
| alleyoop | 96Bulls ingest: receives session manifests and chunks and lands them in the Locker (S3) |
| relayauth | Self-hosted RelayAuth issuer: immutable image, externally managed keys, single-writer SQLite, and optional JWKS ingress |
helm repo add agentworkforce https://AgentWorkforce.github.io/helm-charts
helm repo updateRelaycast is the minimal self-hosted core. Relayflows and 96Bulls are optional add-ons and are not installed with it:
kubectl create namespace relaycast
helm install relaycast agentworkforce/relaycast -n relaycast \
--set baseUrl=https://relay.example.comSee charts/relaycast/README.md for private access, persistence, and composition guidance. The staged AWS-first/GCP-second adapter plan and its proof gates are in docs/self-hosted-reference-architecture.md.
helm install relayfile agentworkforce/relayfile \
--set secrets.internalHmacSecret=<strong-secret> \
--set secrets.productionDsn='postgres://user:pass@host:5432/dbname?sslmode=require' \
--set auth.jwksUrl=https://auth.relay.example.com/.well-known/jwks.jsonSee charts/relayfile/README.md for the full parameter reference.
Bundle a flow, the Relayflows runtime CLI, and its harness CLIs into an image, then run it as a Kubernetes Job. This default mode does not use Agent Relay Cloud:
helm install relayflows agentworkforce/relayflows \
--namespace relayflows --create-namespace \
--set image.repository=registry.example.com/acme/my-flow \
--set image.tag=sha-0123456789abcdef \
--set runtimeInstaller.enabled=false \
--set standalone.flow.path=/app/flows/customer.flow.tsSee charts/relayflows/README.md for ConfigMap flows, model credentials, resuming runs, and optional Cloud worker mode.
One release runs everything. Prerequisite: the CloudNativePG operator (see
deps/PINS.md). Then create one Secret and install:
kubectl create namespace 96bulls
read -rsp 'SUMMARY_MASTER_KEYS (<key-id>:<64-hex>): ' SUMMARY_MASTER_KEYS; printf '\n'
printf %s "$SUMMARY_MASTER_KEYS" | kubectl -n 96bulls create secret generic 96bulls-locker \
--from-literal=rootUser=<user> \
--from-literal=rootPassword=<password> \
--from-file=SUMMARY_MASTER_KEYS=/dev/stdin
unset SUMMARY_MASTER_KEYS
helm install bulls agentworkforce/96bulls -n 96bulls \
--set global.tenant.orgId=<org> \
--set alleyoop.auth.jwksUrl=https://<relayauth>/.well-known/jwks.jsonThe charts default to ghcr.io/agentworkforce/96bulls-services, pinned by
digest. That hosted-service package is private. For direct installs, create a
docker-registry Secret and add --set worker.imagePullSecrets[0].name=<secret> --set alleyoop.imagePullSecrets[0].name=<secret>. When overriding either app's
image repository or tag, also clear its shipped digest with
--set worker.image.digest= --set alleyoop.image.digest= (or supply matching
digests). Hosted EKS installs instead set both worker.image.repository and
alleyoop.image.repository to the fully qualified ECR repository plus its
matching digest; EKS nodes authenticate through their node role, so that path
does not need an image pull Secret. The OpenTelemetry collector is off by default; set
global.otel.endpoint to your backend, or set both otel.enabled=true and
global.otel.endpoint=http://otel-collector:4318 to use the bundled collector.
Its safe default exporter drops telemetry; replace otel.config.exporters and
the pipeline exporters with your backend configuration outside local testing.
Bob Costas reads SUMMARY_MASTER_KEYS from a Secret and stores only encrypted
summary objects in the Locker; no key value belongs in Helm values. The worker
chart projects that Secret key as a read-only file into Bob Costas only. Set
worker.summaryEncryption.existingSecret when the key is not in the shared
Secret (required for pod-identity installs with no worker.existingSecret).
HoopDb credentials come from the Secret CloudNativePG generates
(hoopdb-app). Bring your own Postgres or S3 by disabling hoopdb or
locker and pointing worker.* / alleyoop.* at your infrastructure. On EKS,
set both app charts' locker.credentials.mode=podIdentity to omit
LOCKER_ACCESS_KEY_ID and LOCKER_SECRET_ACCESS_KEY; the AWS SDK then uses its
workload identity credential chain. Leave locker.endpoint empty to use the
native AWS S3 endpoint for locker.region. Unless the cluster uses EKS Auto
Mode, first install the
EKS Pod Identity Agent.
Create the IAM associations for the rendered worker and alleyoop
ServiceAccounts before deploying the workloads, because EKS injects the Pod
Identity configuration when each Pod starts. If associations are added later,
restart the app Pods and recreate any failed locker-init Job. If the S3 bucket
is already created and versioned, also set worker.lockerInit.enabled=false;
otherwise the worker role needs permission to create the bucket and enable
versioning.
ci/96bulls-umbrella/smoke.sh runs the umbrella on kind.
Both charts are hook-free, so helm install and Alchemy's
helm template --no-hooks + server-side apply behave the same. Create a
Secret with HOOPDB_URL, LOCKER_ACCESS_KEY_ID,
LOCKER_SECRET_ACCESS_KEY and SUMMARY_MASTER_KEYS first (or enable
externalSecrets). SUMMARY_MASTER_KEYS is referenced only by Bob Costas.
For workload identity, set locker.credentials.mode=podIdentity and provide
hoopdb.existingSecret; existingSecret may then be empty. This suppresses
both static Locker credential environment variables in every workload and in
the worker's locker-init Job. locker.endpoint may also be empty to select the
native AWS S3 endpoint.
The default image
ghcr.io/agentworkforce/96bulls-services(pinned by digest) is private: add--set imagePullSecrets[0].name=<secret>to both commands. If you overrideimage.repositoryorimage.tag, also setimage.digest=or supply the matching digest.
helm install bulls agentworkforce/96bulls-worker -n 96bulls --create-namespace \
--set existingSecret=96bulls \
--set locker.endpoint=http://locker:9000 \
--set tenant.orgId=<org>
helm install alleyoop agentworkforce/alleyoop -n 96bulls \
--set existingSecret=96bulls \
--set locker.endpoint=http://locker:9000 \
--set tenant.orgId=<org> \
--set auth.jwksUrl=https://<relayauth>/.well-known/jwks.jsonci/96bulls/smoke.sh runs the whole pipeline on kind, with CloudNativePG
(Postgres 17), MinIO and an OpenTelemetry collector. Third-party charts and
images are pinned once, in deps/.
Charts are packaged and published to GitHub Pages via helm/chart-releaser-action on every merge to main, after the same lint checks that run on pull requests pass. chart-releaser detects changed chart versions automatically — each service chart is released independently.
Each released chart is also pushed to GHCR as an OCI artifact:
helm install <release> oci://ghcr.io/agentworkforce/charts/<chart> --version <version>Add new service charts under charts/<service-name>/. Open a PR against main. Merges are gated by Khaliq.
Every chart must install correctly through helm template --no-hooks plus server-side apply, which is how Alchemy's Kubernetes.HelmChart installs it. Pull requests run .github/workflows/lint.yml:
helm lint --strict, once percharts/<chart>/ci/*-values.yaml(or with defaults when there are none).- No Helm hooks. Hooks never run under
--no-hooks; use a plain Job instead (see96bulls-worker). - Deterministic renders. No
randAlphaNum,lookupornow, or every render looks like drift. kubeconform, plus a server-side dry-run apply on kind.- A version bump for any chart change outside
ci/.
Run the same checks locally with scripts/ci/charts.sh static and scripts/ci/charts.sh dryrun (the dry run needs a cluster in the current kube context).