Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 charts/openhands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ This Helm chart deploys the complete OpenHands stack, including all required dep
See the [values.yaml](values.yaml) file for the full list of configurable parameters.
Make sure to update all values marked with "REQUIRED" comments.

### Organization condenser defaults

Set `orgDefaults.condenser.maxTokens` to add a token-based condensation threshold for applicable OpenHands organization settings. Any positive integer is accepted. Condensation triggers on the smaller of this value and the agent LLM's effective input limit, so a value above that limit has no effect; it does not change the model context window, and event-count condensation may still occur first.

These values only take effect from the app version that reads the `OPENHANDS_ORG_DEFAULTS_CONDENSER_*` environment variables. On earlier app versions they render into the pod but do nothing.

```yaml
orgDefaults:
condenser:
maxTokens: 200000
applyToExisting: true
overwriteExisting: true
```

New applicable OpenHands org settings receive the configured value. Existing org rows are only updated when `applyToExisting: true`; existing non-null values are only replaced when `overwriteExisting: true`. Missing and JSON-null `condenser.max_tokens` values are treated as unset.

Reconciliation runs on every app start while these values remain in the release. If `overwriteExisting: true` stays in a site values file, a pod restart, rollout, HPA scale-up, or node eviction can overwrite org-admin UI changes made after the initial rollout. Remove `applyToExisting`/`overwriteExisting` after the rollout completes, or remove `orgDefaults.condenser.maxTokens` to stop future defaulting/reconciliation.

`overwriteExisting: true` is destructive for prior org-level `max_tokens` values. Take a database backup before enabling it. Removing `orgDefaults.condenser.maxTokens` later does not restore previous org-specific values; restore from backup or run corrective SQL if rollback is required.

### Email (Resend)

To enable organization invitation emails via Resend, set `resend.enabled: true` and create a Kubernetes secret named `resend-api-key` with key `resend-api-key` containing your Resend API key. The secret name can be overridden with `resend.auth.existingSecret`.
Expand Down
9 changes: 9 additions & 0 deletions charts/openhands/templates/_env.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{{- define "openhands.env.defaults" }}
{{- $integrations := .Values.integrations | default dict }}
{{- $orgCondenser := (.Values.orgDefaults | default dict).condenser | default dict }}
- name: RUNTIME
value: remote
- name: OH_SANDBOX_SPEC_KIND
Expand Down Expand Up @@ -34,6 +35,14 @@
- name: OH_DAILY_CONVERSATION_LIMIT
value: {{ .Values.dailyConversationLimit | quote }}
{{- end }}
{{- if ne $orgCondenser.maxTokens nil }}
- name: OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS
value: {{ $orgCondenser.maxTokens | quote }}
- name: OPENHANDS_ORG_DEFAULTS_CONDENSER_APPLY_TO_EXISTING
value: {{ $orgCondenser.applyToExisting | default false | quote }}
- name: OPENHANDS_ORG_DEFAULTS_CONDENSER_OVERWRITE_EXISTING
value: {{ $orgCondenser.overwriteExisting | default false | quote }}
{{- end }}
- name: DUPLICATE_EMAIL_CHECK
value: {{ .Values.duplicateEmailCheck | quote }}
- name: OH_RESOLVER_LABEL
Expand Down
7 changes: 7 additions & 0 deletions charts/openhands/templates/validations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ are never rendered, so a server-less release must not trip them.
{{- if and .Values.enabled .Values.enterpriseSSO.idpMetadataUrl (not (include "openhands.keycloakProvisionRealm" .)) -}}
{{- fail "enterpriseSSO.idpMetadataUrl requires chart-managed Keycloak realm provisioning. Set keycloak.enabled=true or keycloak.provisionRealm=true, or leave enterpriseSSO.idpMetadataUrl empty and manage the provider manually." -}}
{{- end -}}
{{- $orgCondenser := (.Values.orgDefaults | default dict).condenser | default dict -}}
{{- if and .Values.enabled (eq $orgCondenser.maxTokens nil) (or $orgCondenser.applyToExisting $orgCondenser.overwriteExisting) -}}
{{- fail "orgDefaults.condenser.maxTokens must be set before applyToExisting or overwriteExisting can be enabled." -}}
{{- end -}}
{{- if and .Values.enabled $orgCondenser.overwriteExisting (not $orgCondenser.applyToExisting) -}}
{{- fail "orgDefaults.condenser.overwriteExisting requires orgDefaults.condenser.applyToExisting=true because overwrite mode only runs while reconciling existing organizations." -}}
{{- end -}}

{{/*
Data-loss guard for the bundled MinIO. Its chart runs the bucket job on
Expand Down
87 changes: 87 additions & 0 deletions charts/openhands/tests/org_defaults_condenser_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
suite: org defaults condenser env wiring
templates:
- deployment.yaml
- litellm-config-script.yaml
tests:
- it: leaves org condenser env unset by default
set:
enabled: true
asserts:
- template: deployment.yaml
notExists:
path: spec.template.spec.containers[0].env[?(@.name=="OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS")]
- template: deployment.yaml
notExists:
path: spec.template.spec.containers[0].env[?(@.name=="OPENHANDS_ORG_DEFAULTS_CONDENSER_APPLY_TO_EXISTING")]
- template: deployment.yaml
notExists:
path: spec.template.spec.containers[0].env[?(@.name=="OPENHANDS_ORG_DEFAULTS_CONDENSER_OVERWRITE_EXISTING")]

- it: renders without org condenser env when the orgDefaults block is null
set:
enabled: true
orgDefaults: null
asserts:
- template: deployment.yaml
notExists:
path: spec.template.spec.containers[0].env[?(@.name=="OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS")]

- it: renders without org condenser env when the condenser block is null
set:
enabled: true
orgDefaults:
condenser: null
asserts:
- template: deployment.yaml
notExists:
path: spec.template.spec.containers[0].env[?(@.name=="OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS")]

- it: wires false rollout flags when only a threshold is configured
set:
enabled: true
orgDefaults.condenser.maxTokens: 200000
asserts:
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS
value: "200000"
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_APPLY_TO_EXISTING
value: "false"
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_OVERWRITE_EXISTING
value: "false"

- it: wires configured org condenser rollout settings
set:
enabled: true
orgDefaults.condenser.maxTokens: 200000
orgDefaults.condenser.applyToExisting: true
orgDefaults.condenser.overwriteExisting: true
asserts:
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_MAX_TOKENS
value: "200000"
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_APPLY_TO_EXISTING
value: "true"
- template: deployment.yaml
contains:
path: spec.template.spec.containers[0].env
content:
name: OPENHANDS_ORG_DEFAULTS_CONDENSER_OVERWRITE_EXISTING
value: "true"
37 changes: 37 additions & 0 deletions charts/openhands/tests/validations_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,43 @@ tests:
- hasDocuments:
count: 0

- it: fails when org condenser rollout flags are set without a threshold
set:
orgDefaults.condenser.maxTokens: null
orgDefaults.condenser.applyToExisting: true
asserts:
- failedTemplate:
errorMessage: "orgDefaults.condenser.maxTokens must be set before applyToExisting or overwriteExisting can be enabled."
- it: fails when org condenser overwrite is enabled without applyToExisting
set:
orgDefaults.condenser.maxTokens: 200000
orgDefaults.condenser.applyToExisting: false
orgDefaults.condenser.overwriteExisting: true
asserts:
- failedTemplate:
errorMessage: "orgDefaults.condenser.overwriteExisting requires orgDefaults.condenser.applyToExisting=true because overwrite mode only runs while reconciling existing organizations."
- it: allows org condenser overwrite when existing-org reconciliation is enabled
set:
orgDefaults.condenser.maxTokens: 200000
orgDefaults.condenser.applyToExisting: true
orgDefaults.condenser.overwriteExisting: true
asserts:
- hasDocuments:
count: 0
- it: renders no resources when the orgDefaults block is null
set:
orgDefaults: null
asserts:
- hasDocuments:
count: 0
- it: renders no resources when the org condenser block is null
set:
orgDefaults:
condenser: null
asserts:
- hasDocuments:
count: 0

# The bundled MinIO bucket job runs on post-upgrade too, so purge: true wipes
# conversation and session data on every ordinary upgrade of a persistent
# install. These cases pin the guard that rejects it.
Expand Down
38 changes: 38 additions & 0 deletions charts/openhands/tests/values_schema_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,41 @@ tests:
asserts:
- failedTemplate:
errorPattern: "ingress/enabled.*want boolean"
- it: rejects orgDefaults.condenser.maxTokens below the minimum
set:
orgDefaults.condenser.maxTokens: 0
asserts:
- failedTemplate:
errorPattern: "orgDefaults/condenser/maxTokens"
- it: rejects negative orgDefaults.condenser.maxTokens
set:
orgDefaults.condenser.maxTokens: -1
asserts:
- failedTemplate:
errorPattern: "orgDefaults/condenser/maxTokens"
- it: accepts orgDefaults.condenser.maxTokens above one million
set:
orgDefaults.condenser.maxTokens: 2000000
asserts:
- notFailedTemplate: {}
- it: rejects a non-integer orgDefaults.condenser.maxTokens
set:
orgDefaults.condenser.maxTokens: "200000"
asserts:
- failedTemplate:
errorPattern: "orgDefaults/condenser/maxTokens"
- it: rejects a non-boolean orgDefaults.condenser.applyToExisting
set:
orgDefaults.condenser.applyToExisting: "true"
asserts:
- failedTemplate:
errorPattern: "orgDefaults/condenser/applyToExisting.*want boolean"
- it: rejects a non-boolean orgDefaults.condenser.overwriteExisting
set:
orgDefaults.condenser.overwriteExisting: "false"
asserts:
- failedTemplate:
errorPattern: "orgDefaults/condenser/overwriteExisting.*want boolean"
- it: rejects a non-integer daily conversation limit
set:
dailyConversationLimit: "twenty"
Expand Down Expand Up @@ -95,5 +130,8 @@ tests:
enterpriseSSO.idpMetadataUrl: https://idp.example.com/metadata
filestore.secretAccessKeyKey: secret-key
laminar.clickhouse.diagnostics.retentionDays: 2
orgDefaults.condenser.maxTokens: 200000
orgDefaults.condenser.applyToExisting: true
orgDefaults.condenser.overwriteExisting: false
asserts:
- notFailedTemplate: {}
13 changes: 13 additions & 0 deletions charts/openhands/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@
"enabled": { "type": "boolean" },
"dailyConversationLimit": { "type": ["integer", "null"] },
"duplicateEmailCheck": { "type": "boolean" },
"orgDefaults": {
"type": ["object", "null"],
"properties": {
"condenser": {
"type": ["object", "null"],
"properties": {
"maxTokens": { "type": ["integer", "null"], "minimum": 1 },
Comment thread
aivong-openhands marked this conversation as resolved.
Resolved
"applyToExisting": { "type": "boolean" },
"overwriteExisting": { "type": "boolean" }
}
}
}
},
"image": {
"type": "object",
"required": ["repository", "tag"],
Expand Down
13 changes: 13 additions & 0 deletions charts/openhands/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ dailyConversationLimit: null
# a plus-address suffix (for example, name+test@example.com and name@example.com).
duplicateEmailCheck: true

# Organization-default condenser settings. These seed newly-created applicable
# OpenHands org settings. Existing org rows are only reconciled when explicitly
# enabled below because that mutates persisted database settings.
orgDefaults:
condenser:
# Adds a token-based condensation threshold; null preserves app/SDK defaults.
# Must be a positive integer when set.
maxTokens: null
# Mutate existing org rows during rollout reconciliation.
applyToExisting: false
# Force existing non-null org-level values to maxTokens during reconciliation.
overwriteExisting: false

datadog:
enabled: false
env: "dev" # Default environment, can be overridden
Expand Down
Loading