Skip to content

Commit 543514e

Browse files
committed
feat(panda-chat): direct sandbox (unprivileged), 20m startup budget, concise prompt
- Both containers run unprivileged: the panda-server `direct` sandbox runs Python as a subprocess isolated by the pod boundary (no dockerd, no privileged sidecar). - startupProbe budget raised to 20m (periodSeconds 10 x failureThreshold 120): a cold panda-server re-embeds ~9k EIP chunks when the proxy Redis cache is down. - Tighten the default systemPrompt: discovery-first, no-fabrication, plain-English. - Sweep dockerd references from values/README/NOTES.
1 parent d4e36aa commit 543514e

5 files changed

Lines changed: 57 additions & 43 deletions

File tree

charts/panda-chat/README.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ AI chat for an Ethereum devnet — an Open-WebUI front end backed by a NousResea
2727
call, one observation per tool call; traces tagged with the devnet name.
2828

2929
When `panda.enabled` is true the pod gains a separate **`panda-server`
30-
sidecar container** (`panda-server` + `dockerd`, privileged — dockerd needs
31-
root); the `hermes` container always runs unprivileged. The bot identity for
32-
the proxy is an Authentik **service account** (e.g. `panda-chat-svc` with a
33-
non-expiring app password) supplied via `credentials.panda.botUsername` /
34-
`credentials.panda.botToken` and materialized in a **dedicated Secret that only
35-
the sidecar mounts** — Hermes executes LLM-driven shell commands, so it never
36-
shares an environment with the bot credential. panda-server mints proxy access
37-
tokens on demand with the OAuth2 `client_credentials` grant and keeps them in
38-
memory only — no seeded credential files, no refresh-token rotation. Hermes
39-
reaches the sidecar on `127.0.0.1:2480` (shared pod network namespace).
30+
sidecar container**; both containers run **unprivileged**. The sandbox is the
31+
`direct` backend — executed Python runs as a subprocess inside the panda-server
32+
container, isolated by the Kubernetes pod boundary (no dockerd, no sandbox
33+
image, no privilege). The bot identity for the proxy is an Authentik **service
34+
account** (e.g. `panda-chat-svc` with a non-expiring app password) supplied via
35+
`credentials.panda.botUsername` / `credentials.panda.botToken` and materialized
36+
in a **dedicated Secret that only the sidecar mounts** — Hermes executes
37+
LLM-driven shell commands, so it never shares an environment with the bot
38+
credential. panda-server mints proxy access tokens on demand with the OAuth2
39+
`client_credentials` grant and keeps them in memory only — no seeded credential
40+
files, no refresh-token rotation. Hermes reaches the sidecar on
41+
`127.0.0.1:2480` (shared pod network namespace).
4042

4143
## Access control
4244

@@ -81,7 +83,8 @@ wires the trusted-header config from a single toggle — see `chat.yaml.j2`.
8183
Two images, both built from [`ethpandaops/chat`](https://github.com/ethpandaops/chat):
8284

8385
- `image.repository` (`ethpandaops/hermes-agent-panda`) — the Hermes agent
84-
with the panda overlay (panda CLI + panda-server + dockerd + entrypoint + skills).
86+
with the panda overlay (panda CLI + panda-server + sandbox Python env +
87+
entrypoint + skills).
8588
- `open-webui.image` (`ethpandaops/open-webui-cf`) — Open-WebUI patched to
8689
forward the Cloudflare Access JWT to the agent (see [Access control](#access-control)).
8790
Its tag must match the `open-webui` subchart appVersion.
@@ -123,7 +126,7 @@ open-webui:
123126
| devnetTools.join.rpcUrl | string | `""` | Public execution RPC URL |
124127
| fullnameOverride | string | `""` | Overrides the chart's computed fullname |
125128
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
126-
| image.repository | string | `"ethpandaops/hermes-agent-panda"` | Panda-overlay Hermes agent image (Hermes + panda CLI + panda-server + dockerd). |
129+
| image.repository | string | `"ethpandaops/hermes-agent-panda"` | Panda-overlay Hermes agent image (Hermes + panda CLI + panda-server). |
127130
| image.tag | string | `""` | Image tag. Defaults to the chart appVersion when empty. |
128131
| imagePullSecrets | list | `[]` | Image pull secrets for the agent image |
129132
| langfuse.baseUrl | string | `""` | Langfuse base URL |
@@ -159,12 +162,10 @@ open-webui:
159162
| open-webui.websocket.enabled | bool | `false` | |
160163
| open-webui.websocket.redis.enabled | bool | `false` | |
161164
| panda.clientId | string | `"panda-proxy"` | OAuth client id at the proxy |
162-
| panda.enabled | bool | `true` | Enable the panda-server sidecar container (privileged; the hermes container is not) |
165+
| panda.enabled | bool | `true` | Enable the panda-server sidecar container (holds the bot credential) |
163166
| panda.issuerUrl | string | `"https://authentik.analytics.production.platform.ethpandaops.io/application/o/panda-proxy/"` | Authentik application issuer the bot service account mints client_credentials tokens against (the trailing slash is part of the issuer — keep it) |
164167
| panda.proxyUrl | string | `"https://panda-proxy.analytics.production.platform.ethpandaops.io"` | Hosted panda-proxy URL (analytics data plane) |
165-
| panda.resources | object | `{"limits":{"cpu":"2000m","memory":"4Gi"},"requests":{"cpu":"200m","memory":"512Mi"}}` | Resources for the panda-server sidecar (panda-server + dockerd + sandboxes) |
166-
| panda.sandboxImage | string | `"ethpandaops/panda:sandbox-v0.31.0"` | Sandbox container image panda-server spawns for Python execution |
167-
| panda.storageDriver | string | `"overlay2"` | dockerd storage driver (overlay2; set to vfs if overlayfs is unavailable in-pod) |
168+
| panda.resources | object | `{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"200m","memory":"512Mi"}}` | Resources for the panda-server sidecar |
168169
| persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes |
169170
| persistence.enabled | bool | `true` | Enable a PVC for /opt/data (Hermes state + panda config/creds/storage) |
170171
| persistence.existingClaim | string | `""` | Use an existing claim instead of creating one |

charts/panda-chat/README.md.gotmpl

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@
2424
call, one observation per tool call; traces tagged with the devnet name.
2525

2626
When `panda.enabled` is true the pod gains a separate **`panda-server`
27-
sidecar container** (`panda-server` + `dockerd`, privileged — dockerd needs
28-
root); the `hermes` container always runs unprivileged. The bot identity for
29-
the proxy is an Authentik **service account** (e.g. `panda-chat-svc` with a
30-
non-expiring app password) supplied via `credentials.panda.botUsername` /
31-
`credentials.panda.botToken` and materialized in a **dedicated Secret that only
32-
the sidecar mounts** — Hermes executes LLM-driven shell commands, so it never
33-
shares an environment with the bot credential. panda-server mints proxy access
34-
tokens on demand with the OAuth2 `client_credentials` grant and keeps them in
35-
memory only — no seeded credential files, no refresh-token rotation. Hermes
36-
reaches the sidecar on `127.0.0.1:2480` (shared pod network namespace).
27+
sidecar container**; both containers run **unprivileged**. The sandbox is the
28+
`direct` backend — executed Python runs as a subprocess inside the panda-server
29+
container, isolated by the Kubernetes pod boundary (no dockerd, no sandbox
30+
image, no privilege). The bot identity for the proxy is an Authentik **service
31+
account** (e.g. `panda-chat-svc` with a non-expiring app password) supplied via
32+
`credentials.panda.botUsername` / `credentials.panda.botToken` and materialized
33+
in a **dedicated Secret that only the sidecar mounts** — Hermes executes
34+
LLM-driven shell commands, so it never shares an environment with the bot
35+
credential. panda-server mints proxy access tokens on demand with the OAuth2
36+
`client_credentials` grant and keeps them in memory only — no seeded credential
37+
files, no refresh-token rotation. Hermes reaches the sidecar on
38+
`127.0.0.1:2480` (shared pod network namespace).
3739

3840
## Access control
3941

@@ -78,7 +80,8 @@ wires the trusted-header config from a single toggle — see `chat.yaml.j2`.
7880
Two images, both built from [`ethpandaops/chat`](https://github.com/ethpandaops/chat):
7981

8082
- `image.repository` (`ethpandaops/hermes-agent-panda`) — the Hermes agent
81-
with the panda overlay (panda CLI + panda-server + dockerd + entrypoint + skills).
83+
with the panda overlay (panda CLI + panda-server + sandbox Python env +
84+
entrypoint + skills).
8285
- `open-webui.image` (`ethpandaops/open-webui-cf`) — Open-WebUI patched to
8386
forward the Cloudflare Access JWT to the agent (see [Access control](#access-control)).
8487
Its tag must match the `open-webui` subchart appVersion.

charts/panda-chat/templates/NOTES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ release name). Set these on the open-webui subchart:
2525

2626
{{- if .Values.panda.enabled }}
2727

28-
Panda is ENABLED — the agent pod is privileged and runs dockerd + panda-server.
28+
Panda is ENABLED — the pod runs an unprivileged panda-server sidecar (the
29+
`direct` sandbox executes Python as a subprocess; no dockerd, no privilege).
2930
Provision the bot identity and fill credentials.panda.* (see the chart README).
3031
{{- end }}

charts/panda-chat/templates/deployment.yaml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ spec:
2121
imagePullSecrets: {{- toYaml . | nindent 8 }}
2222
{{- end }}
2323
# Per-container security contexts below; fsGroup lets every container
24-
# (and the init containers) share the PVC. Hermes is ALWAYS unprivileged
25-
# — only the panda-server sidecar is privileged (dockerd).
24+
# (and the init containers) share the PVC. Both containers run
25+
# unprivileged — the panda-server `direct` sandbox executes Python as a
26+
# subprocess, isolated by the pod boundary (no dockerd, no privilege).
2627
securityContext:
2728
fsGroup: 10000
2829
initContainers:
@@ -142,11 +143,18 @@ spec:
142143
name: {{ include "panda-chat.pandaSecretName" . }}
143144
# exec probes: panda-server binds 127.0.0.1, which kubelet httpGet
144145
# (pod IP) can't reach.
146+
# Generous startup budget (20 min): the EIP semantic index (9k chunks)
147+
# is built at boot via the hosted proxy's embedding endpoint. When the
148+
# proxy's Redis cache is unreachable EVERY chunk is a cache miss, so a
149+
# cold boot re-embeds everything (~10 min observed) — plus first-run
150+
# EIP/spec/schema fetches. A tight budget kills panda-server mid-embed
151+
# → restart → re-embed → loop. (The real fix is the proxy Redis; this
152+
# just lets a cold boot finish.)
145153
startupProbe:
146154
exec:
147155
command: ["sh", "-c", "curl -sf http://127.0.0.1:2480/health"]
148-
periodSeconds: 5
149-
failureThreshold: 12
156+
periodSeconds: 10
157+
failureThreshold: 120
150158
livenessProbe:
151159
exec:
152160
command: ["sh", "-c", "curl -sf http://127.0.0.1:2480/health"]

charts/panda-chat/values.yaml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ network: ""
1212
chainId: ""
1313

1414
image:
15-
# -- Panda-overlay Hermes agent image (Hermes + panda CLI + panda-server + dockerd).
15+
# -- Panda-overlay Hermes agent image (Hermes + panda CLI + panda-server).
1616
repository: ethpandaops/hermes-agent-panda
1717
# -- Image tag. Defaults to the chart appVersion when empty.
1818
tag: ""
@@ -37,17 +37,18 @@ llm:
3737

3838
# -- System prompt for the agent. `{{ network }}` is substituted at render time.
3939
systemPrompt: |
40-
You are the EthPandaOps assistant for the Ethereum devnet "{{ network }}".
41-
Help users understand devnet state and use the EthPandaOps tooling:
42-
query live data with the `panda` skill, fund accounts with the `faucet`
43-
skill, and join the network with the `join-devnet` skill. Be concise and
44-
always scope data queries to this devnet.
45-
46-
# Panda integration. When enabled the pod gains a separate privileged
47-
# "panda-server" sidecar container (panda-server + dockerd) holding the bot
48-
# credential; the hermes container stays unprivileged and credential-free.
40+
You are the EthPandaOps assistant for the Ethereum devnet "{{ network }}", scoped to it alone.
41+
Tools: `panda` (query live data), `faucet` (fund accounts), `join-devnet` (join the network).
42+
Discover before querying (the `panda` skill shows how); never invent APIs, tables, or node names.
43+
Report exactly what the tools return — never fabricate; if data is missing or sources disagree, say so.
44+
Lead with the numbers, in plain English. Be concise.
45+
46+
# Panda integration. When enabled the pod gains a separate "panda-server"
47+
# sidecar container holding the bot credential; the hermes container stays
48+
# credential-free. Both run unprivileged — the `direct` sandbox runs Python as
49+
# a subprocess, isolated by the pod boundary (no dockerd, no privilege).
4950
panda:
50-
# -- Enable the panda-server sidecar container (privileged; the hermes container is not)
51+
# -- Enable the panda-server sidecar container (holds the bot credential)
5152
enabled: true
5253
# -- Hosted panda-proxy URL (analytics data plane)
5354
proxyUrl: "https://panda-proxy.analytics.production.platform.ethpandaops.io"

0 commit comments

Comments
 (0)