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
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ CONTAINER_LLM_MODEL=
# Increase if you see truncated specs/PRDs (default 4096 is often too small)
LLM_MAX_TOKENS=16384

# Optional provider-neutral per-stage policy (JSON). Credentials remain in the
# provider-native variables above; credential_ref is an identifier, never a key.
# MODEL_CONNECTIONS={"vertex-prod":{"backend":"vertex-ai","credential_ref":"gcp-adc","project":"your-gcp-project-id","location":"global","allowed_models":["gemini-3.5-pro","gemini-3.5-flash"]}}
# MODEL_DEFAULT={"connection":"vertex-prod","model":"gemini-3.5-flash"}
# MODEL_POLICY={"generate_prd":{"connection":"vertex-prod","model":"gemini-3.5-pro"}}

# =============================================================================
# MCP Servers Configuration
# =============================================================================
Expand Down Expand Up @@ -217,6 +223,20 @@ CONTAINER_LANGCHAIN_VERBOSE=false
# inspect commands (podman logs, podman export). Remember to clean up with podman rm.
FORGE_CONTAINER_KEEP=false

# Kubernetes sandbox driver (used when the Forge worker runs in Kubernetes).
# The worker and sandbox Jobs must mount the same workspace PVC.
# FORGE_SANDBOX_DRIVER=kubernetes
# WORKSPACE_BASE_DIR=/mnt/workspaces
# K8S_NAMESPACE=forge
# K8S_WORKSPACE_PVC=forge-workspaces
# K8S_WORKSPACE_BASE_PATH=/mnt/workspaces
# K8S_IMAGE_PULL_SECRETS=
# K8S_SERVICE_ACCOUNT=forge-runner
# Optional Google ADC Secret mounted by the Kubernetes driver into sandbox Jobs:
# K8S_GOOGLE_CREDENTIALS_SECRET=google-adc
# K8S_GOOGLE_CREDENTIALS_KEY=forge-gcp-credentials.json
# K8S_GOOGLE_CREDENTIALS_MOUNT_PATH=/etc/forge-gcp-credentials.json

# =============================================================================
# Git Configuration (for commits made by Forge)
# =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ COPY pyproject.toml README.md ./
COPY src/ ./src/

RUN pip install --no-cache-dir build && \
pip wheel --no-cache-dir --wheel-dir /wheels -e .
pip wheel --no-cache-dir --wheel-dir /wheels -e ".[k8s]"

# Production stage
FROM python:3.11-slim
Expand Down
6 changes: 6 additions & 0 deletions charts/forge/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: forge
description: Forge API and Kubernetes sandbox worker
type: application
version: 0.1.0
appVersion: "0.1.0"
39 changes: 39 additions & 0 deletions charts/forge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Forge Helm chart

This chart deploys the Forge API, worker, Redis Stack, worker RBAC, and the
workspace PVC used by Kubernetes sandbox Jobs. It targets OpenShift by default.

Create the namespace and application secret before installing. Keep secrets out
of Helm values and source control. Values consumed as JSON, such as
`MODEL_CONNECTIONS`, must contain raw JSON in the env file; do not surround
them with shell quotes because `oc --from-env-file` preserves those quotes.

```bash
oc new-project forge
oc create secret generic forge-env --from-env-file=.env -n forge
helm upgrade --install forge charts/forge -n forge
```

For Vertex AI, create a Secret from the same service-account credential used by
the existing Forge deployment and enable the optional mount. The chart mounts
the credential into both the worker and every sandbox Job:

```bash
oc create secret generic google-adc \
--from-file=forge-gcp-credentials.json=/path/to/forge-gcp-credentials.json \
-n forge
helm upgrade --install forge charts/forge -n forge \
--set googleCredentials.enabled=true
```

Build and push two images before installing:

* `image.repository`: the Forge API/worker image built from `Dockerfile`
* `sandboxImage.repository`: the task image built from `containers/Containerfile`

Override repositories, tags, storage class, or Route settings in a local values
file. The worker uses in-cluster service-account authentication; do not mount a
kubeconfig into it.

The workspace PVC is retained when the Helm release is removed. Delete it
explicitly when its workspaces are no longer needed.
13 changes: 13 additions & 0 deletions charts/forge/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{- define "forge.name" -}}forge{{- end }}
{{- define "forge.fullname" -}}{{ .Release.Name }}{{- end }}
{{- define "forge.labels" -}}
app.kubernetes.io/name: {{ include "forge.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "forge.image" -}}
{{ printf "%s:%s" .Values.image.repository .Values.image.tag }}
{{- end }}
{{- define "forge.sandboxImage" -}}
{{ printf "%s:%s" .Values.sandboxImage.repository .Values.sandboxImage.tag }}
{{- end }}
80 changes: 80 additions & 0 deletions charts/forge/templates/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "forge.fullname" . }}-api
labels:
{{- include "forge.labels" . | nindent 4 }}
app.kubernetes.io/component: api
spec:
replicas: {{ .Values.api.replicas }}
selector:
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: api
template:
metadata:
annotations:
{{- toYaml .Values.podAnnotations | nindent 8 }}
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: api
spec:
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: {{ include "forge.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
envFrom:
- secretRef:
name: {{ .Values.existingSecret }}
env:
- name: REDIS_URL
value: redis://{{ include "forge.fullname" . }}-redis:6379/0
ports:
- name: http
containerPort: 8000
readinessProbe:
httpGet: {path: /api/v1/ready, port: http}
livenessProbe:
httpGet: {path: /api/v1/live, port: http}
resources:
{{- toYaml .Values.resources.api | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "forge.fullname" . }}-api
spec:
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: api
ports:
- name: http
port: {{ .Values.api.service.port }}
targetPort: http
{{- if .Values.api.route.enabled }}
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: {{ include "forge.fullname" . }}
spec:
{{- with .Values.api.route.host }}
host: {{ . }}
{{- end }}
to:
kind: Service
name: {{ include "forge.fullname" . }}-api
port:
targetPort: http
tls:
termination: {{ .Values.api.route.tlsTermination }}
insecureEdgeTerminationPolicy: Redirect
{{- end }}
26 changes: 26 additions & 0 deletions charts/forge/templates/rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "forge.fullname" . }}-worker
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["create", "get", "list", "watch", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "forge.fullname" . }}-worker
subjects:
- kind: ServiceAccount
name: {{ .Values.worker.serviceAccountName }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "forge.fullname" . }}-worker
65 changes: 65 additions & 0 deletions charts/forge/templates/redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{{- if .Values.redis.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "forge.fullname" . }}-redis
labels:
{{- include "forge.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
template:
metadata:
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
spec:
containers:
- name: redis
image: {{ .Values.redis.image | quote }}
ports:
- name: redis
containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
{{- if .Values.redis.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "forge.fullname" . }}-redis
{{- else }}
emptyDir: {}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "forge.fullname" . }}-redis
spec:
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
ports:
- name: redis
port: 6379
{{- if .Values.redis.persistence.enabled }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "forge.fullname" . }}-redis
spec:
accessModes: ["ReadWriteOnce"]
{{- with .Values.redis.persistence.storageClassName }}
storageClassName: {{ . }}
{{- end }}
resources:
requests:
storage: {{ .Values.redis.persistence.size }}
{{- end }}
{{- end }}
13 changes: 13 additions & 0 deletions charts/forge/templates/serviceaccounts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.worker.serviceAccountName }}
labels:
{{- include "forge.labels" . | nindent 4 }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.sandbox.jobServiceAccountName }}
labels:
{{- include "forge.labels" . | nindent 4 }}
Loading