Totoro 16d36ccd21
feat(sandbox): add bounded AIO prewarming and lifecycle benchmarks (#6998)
* fix(benchmark): validate sandbox lifecycle and concurrent measurements

* feat(sandbox): prewarm likely AIO tool turns

(cherry picked from commit 3a1017ed032ed9331987bc3dc61f23dcdac4e905)

* fix(sandbox): bind bounded prewarming to execution leases

* docs: trim frontend guidance below its existing size limit

* fix(benchmark): separate failed lifecycle stage timings

* test(sandbox): measure prewarm overlap and unused resources through agent graphs

* fix(sandbox): finish lease and sampler cleanup after cancellation or errors

* docs(benchmark): distinguish initialization wait from model overlap

* docs(helm): align the configuration example with version 61

* fix(sandbox): preserve synchronous agent graphs when prewarming is enabled

* test(cargo): isolate native fixtures from inherited make jobserver flags

* fix(sandbox): require managed cleanup before prewarming

* fix(sandbox): log denied prewarming as an expected skip

* fix(benchmark): filter provider summary input records

---------

Co-authored-by: nothingyuancando <1579035512@qq.com>
2026-10-18 08:10:56 +08:00

572 lines
25 KiB
YAML

# DeerFlow Helm chart values
#
# Copy to my-values.yaml, edit, and install with:
# helm install deer-flow deploy/helm/deer-flow -n deer-flow --create-namespace -f my-values.yaml
# -- Target namespace (also used as the K8s sandbox namespace for provisioner-spawned Pods).
namespace: deer-flow
# -- Image registry & tag for the three DeerFlow images you build+push.
# Required: set `registry` to your registry (e.g. ghcr.io/yourorg).
image:
registry: "" # REQUIRED, e.g. ghcr.io/yourorg
tag: "latest"
pullPolicy: IfNotPresent
# -- Existing image pull secrets, e.g. [{ name: regcred }]
pullSecrets: []
# Image names match what .github/workflows/container.yaml publishes on GHCR
# as ${repository}-<name> (e.g. ghcr.io/<owner>/deer-flow-backend). Set
# `registry` to the owner prefix (e.g. ghcr.io/<owner>) and `tag` to consume
# the published images.
gatewayImage: deer-flow-backend
frontendImage: deer-flow-frontend
provisionerImage: deer-flow-provisioner
# -- Gateway (backend) deployment.
gateway:
# Replica count. 1 is the conservative default; >1 is supported for the web
# and API path once the prerequisites below hold, all of which this chart
# sets in `config` by default: Postgres (`database` + `checkpointer`),
# `run_events.backend: db`, `run_ownership.heartbeat_enabled: true` and the
# Redis stream bridge (sandbox ownership leases are inferred from it). The
# chart also exports DEER_FLOW_MULTI_INSTANCE=true when replicas > 1 so the
# Gateway's startup gate enforces those prerequisites instead of staying
# inert (its worker count only sees one Pod). IM channels follow
# `channels.leader_lease` in `config` (default `auto`, on with replicas > 1):
# one Pod holds each platform connection and another takes over when it
# dies; the startup gate refuses `enabled: false` while channels are enabled
# (see the chart README). Still single-instance: the WeChat QR login and
# browser tools.
# Multi-node clusters also need `persistence.home.accessMode: ReadWriteMany`,
# and `agent_storage.backend: db` keeps custom agents visible on every Pod.
# Memory stays on that shared volume, but DeerMem's SQLite retrieval index is
# Pod-local (`memory.backend_config.retrieval_index_path` in `config`, on an
# emptyDir at /var/lib/deerflow/memory-index); keep that line when you
# override `config`, or the Gateway warns at startup and every Pod shares one
# WAL database over the network filesystem.
# Scheduler multi_instance recovery is safe only when its config
# prerequisites are met; max_concurrent_runs is shared across Pods then.
replicas: 1
# Explicit multi-instance declaration (boolean). Leave unset (null) to derive
# it from `replicas` above. Set `true` before scaling the gateway with
# `kubectl scale` or an HPA: those change the replica count without a Helm
# upgrade, so the derived DEER_FLOW_MULTI_INSTANCE, the PodDisruptionBudget
# and the required shared AUTH_JWT_SECRET would otherwise keep their
# single-replica rendering. Note the PodDisruptionBudget (minAvailable: 1)
# is rendered as soon as this is true: until the scale-up actually happens
# it blocks voluntary evictions and node drains of the single Pod. Set
# podDisruptionBudget.enabled: false for that interim if you need to drain.
multiInstance: null
# Rollout strategy. Surge-then-drain keeps one Pod serving while a peer
# starts; with run-ownership heartbeats the new Pod leaves the old Pod's live
# runs alone (their leases keep renewing) while the old Pod drains them.
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# PodDisruptionBudget, rendered only for a multi-instance gateway (replicas
# > 1 or multiInstance: true; a budget of 1 on a single replica would block
# node drains). minAvailable is an integer or a percentage string such as
# "50%" (quoted; a plain `int` cast would turn it into 0) and falls back to
# 1 when unset. 0 and "0%" fail the render -- a zero budget protects
# nothing -- so disable the budget instead.
podDisruptionBudget:
enabled: true
minAvailable: 1
# uvicorn --timeout-graceful-shutdown (seconds): how long open HTTP
# connections (long SSE streams included) may hold up lifespan shutdown
# after SIGTERM before uvicorn closes them. Without a bound one idle SSE
# client can push the whole shutdown past the grace period into SIGKILL.
# 0 closes open connections immediately on SIGTERM.
uvicornGracefulShutdownSeconds: 10
# Pod termination grace period (seconds). MUST exceed the Gateway's graceful
# shutdown work: preStopSleepSeconds + uvicornGracefulShutdownSeconds + the
# lifespan's worst case (five 5s-bounded hooks: trash sweep, notification
# worker, channel service, browser sessions, MCP session pool; the 1s
# retrieval-warm wait; the 5s run drain; the memory queue drain,
# memory.shutdown_flush_timeout_seconds, default 30s -- about 61s) + buffer.
# backend/tests/_gateway_shutdown_budget.py pins the arithmetic. K8s defaults
# to 30s, which SIGKILLs the memory drain mid-flight and silently re-introduces
# the memory loss that shutdown_flush is fixing. Raise this when you raise the
# memory timeout. See gateway-deployment.yaml + README "Graceful shutdown".
terminationGracePeriodSeconds: 90
# preStop hook sleep (seconds) before SIGTERM, so the Service/ingress
# deregisters the pod and in-flight HTTP requests drain before graceful
# shutdown begins (which runs the memory queue drain). 0 disables the hook.
# Counts against terminationGracePeriodSeconds.
preStopSleepSeconds: 5
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
# -- Frontend (Next.js) deployment.
frontend:
replicas: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
# -- nginx reverse-proxy deployment (preserves compose routing).
nginx:
replicas: 1
image:
repository: nginx
tag: alpine
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
service:
# -- Service type fronting nginx. `LoadBalancer` on Docker Desktop / kind /
# OrbStack routes to localhost (no Ingress controller needed). `ClusterIP`
# pairs with the Ingress resource. `NodePort` exposes on a node port.
type: ClusterIP
port: 2026
loadBalancerIP: ""
nodePort: ""
# -- Sandbox provisioner (K8s-native code execution). Creates sandbox Pods in
# this namespace via a ServiceAccount + RBAC.
provisioner:
enabled: true
# The gateway authenticates to the provisioner with PROVISIONER_API_KEY,
# which the chart generates into the `<release>-app` Secret (see
# existingAppSecret) and injects into both Pods; `config.sandbox` below
# references it as `provisioner_api_key: $PROVISIONER_API_KEY`. The
# provisioner rejects every /api/* request without a matching key. With
# `enabled: false` the gateway takes PROVISIONER_API_KEY from `secrets` /
# `existingSecret` when you supply one (external provisioner); otherwise
# its start command defaults the variable to "", so the default `config`
# below still loads even when a user-managed Secret lacks the key.
# Replica count. 1 is the conservative default, matching the gateway; >1 is
# safe as the provisioner stands: it keeps no state of its own between
# requests -- the sandbox Pods and Services it labels (`app=deer-flow-sandbox`,
# `sandbox-id`) are the only registry, every handler reads them back from
# the API server, and create tolerates the 409 AlreadyExists a concurrent
# creator on another replica produces (the race several gateway Pods already
# run against one provisioner). NodePorts are allocated by the API server,
# never by the provisioner. The `provisioner` Service spreads requests over
# every replica, so with 1 replica this is the last single point of failure
# in a multi-replica gateway deployment: while its Pod restarts or its node
# drains no sandbox can be created or discovered. Scaling it changes nothing
# else in the chart, so there is no multiInstance switch here.
replicas: 1
# Rollout strategy. Surge-then-drain keeps one Pod answering sandbox calls
# while a peer starts; a replica has nothing to hand over beyond its
# in-flight requests, so the default termination grace period is enough.
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# PodDisruptionBudget, rendered only for replicas > 1 (a budget of 1 on a
# single replica would block node drains). minAvailable is an integer or a
# percentage string such as "50%" and falls back to 1 when unset; 0 and
# "0%" fail the render -- a zero budget protects nothing -- so disable the
# budget instead. Scaling with `kubectl scale` instead of `replicas` leaves
# the budget unrendered.
podDisruptionBudget:
enabled: true
minAvailable: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# -- AIO-compatible sandbox container image used for sandbox Pods.
sandboxImage: "enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"
# -- Service type for per-sandbox Services. `ClusterIP` (default) exposes
# sandboxes only inside the cluster via Service DNS - correct for the
# in-cluster gateway and avoids binding the code-execution surface on
# every node's interfaces. `NodePort` is the Docker-Compose/hybrid escape
# hatch (used when the gateway is not in K8s); pair with `nodeHost`.
sandboxServiceType: "ClusterIP"
# -- Host the gateway uses to reach sandbox NodePorts. Only consulted when
# `sandboxServiceType: NodePort`. Empty (default) falls back to the
# provisioner pod's node IP via the Kubernetes downward API, which routes
# on most clusters because a NodePort is exposed on every node. Set
# explicitly only when pod->node-IP traffic is blocked by your
# CNI/network policy. In Docker Compose this is `host.docker.internal`.
nodeHost: ""
# -- Sandbox container port (must match the sandboxImage's listening port).
sandboxPort: 8080
# -- Optional lark-cli init image (Pattern A). Empty (default) leaves the
# sandbox without a lark-cli runtime, so an in-sandbox `lark-cli` call
# fails with "command not found". When set (e.g.
# `deer-flow/lark-cli-init:v1.0.65`), sandbox Pods requesting the runtime
# get an init container + shared emptyDir instead of a hostPath/PVC mount.
# Mirrors compose's `LARK_CLI_INIT_IMAGE`. See `docker/lark-cli-init/`.
larkCliInitImage: ""
# -- Optional lark-cli broker image (Pattern B, issue #4338). When set, the
# sandbox gets a shim + a `lark-cli-broker` sidecar that holds the
# credentials, so the plaintext config/data dirs are never mounted into
# the sandbox. Supersedes `larkCliInitImage` when both are set. Empty
# (default) ⇒ broker off. Mirrors compose's `LARK_CLI_BROKER_IMAGE`.
# See `docker/lark-cli-broker/`.
larkCliBrokerImage: ""
# -- PostgreSQL database. Bundled mode (default) deploys a single-instance
# postgres StatefulSet; set `enabled: false` to use an external managed DB.
# The gateway reads DATABASE_URL from the resolved Secret and config.yaml
# references it as $DATABASE_URL in database.postgres_url.
postgresql:
# -- Deploy a bundled postgres StatefulSet. Disable to use an external DB.
enabled: true
image:
repository: postgres
tag: "16"
auth:
database: deerflow
username: deerflow
# -- Password auto-generated (32 chars, persisted across upgrades) when
# empty. Ignored if existingSecret is set.
password: ""
# -- Use an existing Secret (key `database-url`, plus `postgres-password`
# in bundled mode) instead of generating one. Skips Secret creation.
existingSecret: ""
primary:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
persistence:
enabled: true
storageClass: "" # "" = cluster default
accessMode: ReadWriteOnce # RWX (NFS) only needed if postgres itself is HA
size: 20Gi
# -- External postgres (used when `enabled: false`). Provide either a full
# `databaseUrl` (chart wraps it into a Secret for you) or an
# `existingSecret` you manage (key `database-url`).
external:
# -- Full DSN, e.g. postgresql://user:pass@host:5432/deerflow. Chart
# writes it into the Secret so it never touches the gateway env directly.
databaseUrl: ""
# -- Secret you manage (key `database-url`). Use this with External Secrets
# / Vault / Sealed Secrets so the DSN never appears in values files.
existingSecret: ""
# -- Redis stream bridge. The gateway stores per-run SSE events in Redis Streams
# so live run events reach a client connected to any gateway pod (cross-pod
# SSE delivery + reconnect replay, PR #3191). Bundled mode (default) deploys
# a single-instance redis StatefulSet; set `enabled: false` to use an external
# managed Redis. The gateway reads DEER_FLOW_STREAM_BRIDGE_REDIS_URL from the
# resolved Secret; config.yaml's stream_bridge.type is `redis` by default.
redis:
# -- Deploy a bundled redis StatefulSet. Disable to use an external Redis.
enabled: true
image:
repository: redis
tag: "7-alpine"
auth:
# -- Empty (default) = no auth, matching the compose deployment (ClusterIP
# isolation only). Set a password to enable AUTH; the DSN becomes
# redis://:<password>@host:6379/0. Ignored if existingSecret is set.
password: ""
# -- Use an existing Secret (key `redis-url`, plus `redis-password` if auth)
# instead of generating one. Skips Secret creation.
existingSecret: ""
primary:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: "1"
memory: 512Mi
persistence:
enabled: true
storageClass: "" # "" = cluster default
accessMode: ReadWriteOnce # RWX (NFS) only needed if redis itself is HA
size: 5Gi
# -- External redis (used when `enabled: false`). Provide either a full
# `redisUrl` (chart wraps it into a Secret for you) or an `existingSecret`
# you manage (key `redis-url`).
external:
# -- Full URL, e.g. redis://:pass@myredis.example:6379/0. Chart writes it
# into the Secret so it never touches the gateway env directly.
redisUrl: ""
# -- Secret you manage (key `redis-url`). Use with External Secrets / Vault
# / Sealed Secrets so the URL never appears in values files.
existingSecret: ""
# -- Persistent volume for runtime state (.deer-flow): sqlite DB, memory,
# custom agents, mutable extensions_config.json, and per-thread user-data.
# Also mounted (PVC mode) into provisioner-spawned sandbox Pods. When
# disabled, Gateway runtime state uses a pod-local emptyDir instead.
persistence:
home:
enabled: true
storageClass: "" # "" = cluster default
accessMode: ReadWriteOnce # Use ReadWriteMany (NFS/etc.) for multi-node
size: 10Gi
# -- Skills library mounted at /app/skills. Default: emptyDir (skills disabled).
# Populate via an existing PVC or ConfigMap, or bake skills into a custom
# gateway image. Provisioner PVC mode references this same claim when set.
skills:
enabled: false
existingClaim: ""
configMap: ""
# -- Provider/channel/search secrets injected as env vars into the gateway.
# Reference them from config.yaml as $VAR. Example:
# secrets:
# OPENAI_API_KEY: "sk-..."
# FEISHU_APP_ID: "cli_xxx"
# FEISHU_APP_SECRET: "xxx"
# GITHUB_TOKEN: "ghp_xxx"
secrets: {}
# -- Use an existing Secret instead of creating one from `secrets` above.
existingSecret: ""
# -- Use an existing Secret for the app secrets instead of generating
# `<release>-app`. It must hold BETTER_AUTH_SECRET (frontend),
# DEER_FLOW_INTERNAL_AUTH_TOKEN, PROVISIONER_API_KEY (the shared gateway ->
# sandbox-provisioner key, required while `provisioner.enabled`) and, for
# more than one gateway instance, AUTH_JWT_SECRET (the session-cookie
# signing key every Pod must share). It should also hold
# DEER_FLOW_CREDENTIALS_KEY (the at-rest key for stored credentials, a
# Fernet key); a multi-instance gateway with channel_connections enabled
# refuses to start without it.
existingAppSecret: ""
# -- Ingress in front of nginx (port 2026). nginx preserves all internal routing.
ingress:
enabled: true
className: "nginx"
host: "deer-flow.example.com"
annotations:
# Allows a 100 MiB .skill archive plus multipart framing at the outer ingress.
nginx.ingress.kubernetes.io/proxy-body-size: "101m"
# Streams request bodies to nginx instead of spooling them at the outer ingress.
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
# Skill validation may perform multiple sequential LLM calls.
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
tls:
enabled: false
secretName: ""
# hosts: [] # defaults to [ingress.host]
# -- DeerFlow config.yaml content. Secrets MUST stay as $VAR references — never
# inline literal secret values here. The default enables provisioner sandbox.
config: |
config_version: 61
log_level: info
recursion_limit: 100
max_recursion_limit: 1000
# Optional provider-agnostic knowledge capability and custom-agent scope selector.
# RAGFlow connection/retrieval settings belong on the knowledge_search tool.
knowledge_base:
enabled: false
scope_selection_enabled: false
models: []
# Both switches are required for conversation-created schedules. Restart pods.
scheduler:
enabled: false
tool_enabled: false
# Example (uncomment & set the matching secret in `secrets`):
# - name: gpt-4
# display_name: GPT-4
# use: langchain_openai:ChatOpenAI
# model: gpt-4
# api_key: $OPENAI_API_KEY
# request_timeout: 600.0
sandbox:
use: deerflow.community.aio_sandbox:AioSandboxProvider
prewarm: false
# Pending/ready-unused speculation per provider instance in each process.
prewarm_max_concurrent: 1
provisioner_url: http://provisioner:8002
# Sent as X-API-Key on every provisioner request. The chart injects the
# generated `<release>-app` key into the gateway and provisioner Pods as
# PROVISIONER_API_KEY; without a matching value the provisioner answers
# 401 and no sandbox can be created.
provisioner_api_key: $PROVISIONER_API_KEY
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
port: 8080
replicas: 3
database:
# PostgreSQL is the default backend (bundled StatefulSet, or external).
# DATABASE_URL is injected from the postgres Secret; $DATABASE_URL is
# resolved by the harness before the config is instantiated.
backend: postgres
postgres_url: $DATABASE_URL
pool_recycle: 300
command_timeout: 30
# The LangGraph Store (cross-thread memory + thread list) reads this legacy
# `checkpointer:` section — it does NOT fall back to `database:` the way the
# checkpointer does. Point it at the same postgres so the Store is shared
# across gateway pods (required for multi-replica operation).
checkpointer:
type: postgres
connection_string: $DATABASE_URL
# Stream bridge: `redis` (default) stores per-run SSE events in Redis Streams
# so live run events reach a client on any gateway pod (cross-pod SSE delivery
# + reconnect replay, PR #3191). The URL is read from the
# DEER_FLOW_STREAM_BRIDGE_REDIS_URL env var injected from the redis Secret.
# Set `type: memory` (and disable the redis chart) for single-pod-only mode.
stream_bridge:
type: redis
# Multi-replica prerequisites (harmless at 1 replica). Heartbeats give each
# run a lease, so a starting Pod only reclaims runs whose owner is really
# gone instead of writing every peer's live run off as an orphan; db run
# events share message history and delivery receipts across Pods. Sandbox
# ownership leases (#4206) are inferred from the Redis stream bridge above.
# Both are startup-only: restart every Gateway Pod together after changing
# them.
run_ownership:
heartbeat_enabled: true
run_events:
backend: db
# Custom agents and managed subagents default to per-Pod files under the
# home volume. Switch to the shared database when running more than one
# replica. From the repository root, use the installed backend environment
# and the Gateway's runtime/configuration selectors to preview the import:
# `uv run --no-sync --project backend python backend/scripts/migrate_agents_to_db.py --dry-run`
# Remove --dry-run after reviewing the preview to perform the import:
# agent_storage:
# backend: db
# Canonical memory (users/{uid}/memory.json plus fact Markdown) lives at the
# default root on the home volume. The derived SQLite FTS5 retrieval index is
# rebuildable and must not be shared between Pods: SQLite WAL is unsupported
# on network filesystems, every Pod start would rebuild the shared index
# under its peers, and one Pod's corruption recovery would delete files the
# others hold open. gateway-deployment.yaml mounts an emptyDir at this path;
# a Pod re-syncs a user's index on its next search after a peer writes.
memory:
backend_config:
retrieval_index_path: /var/lib/deerflow/memory-index
verification:
receipts_enabled: true
receipts_render_mode: "delegation_only"
judge_enabled: false
judge_model_name: null
# -- Tools configuration. The agent gets NO tools unless they're listed here
# (BUILTIN_TOOLS only adds present_file + ask_clarification). The file/bash
# tools run inside the AIO sandbox configured above. The web tools
# (web_search, web_fetch, image_search) need no API key but require outbound
# internet from the gateway pod - swap backends or remove entries for
# air-gapped clusters (see config.example.yaml).
tool_groups:
- name: web
- name: file:read
- name: file:write
- name: bash
# - name: knowledge # Enable with the RAGFlow tool below.
tools:
# Optional tenant-shared, read-only RAGFlow retrieval. Put RAGFLOW_API_KEY
# in `secrets`. `datasets` is an optional stable-ID allowlist; omit it to
# search all tenant-visible datasets. An explicit empty list is invalid.
# Empty datasets are skipped; remaining
# datasets are grouped by embedding model with up to four parallel retrieval
# requests and one global `page_size` limit. Multi-group scores are omitted
# because they are not comparable. The Agent cannot see the IDs.
# - name: knowledge_search
# group: knowledge
# use: deerflow.community.ragflow.tools:knowledge_search_tool
# base_url: http://ragflow:9380
# api_key: $RAGFLOW_API_KEY
# datasets: # Optional operator-controlled allowlist
# - 0123456789abcdef0123456789abcdef
# timeout: 30
# page_size: 8
# similarity_threshold: 0.2
# vector_similarity_weight: 0.3
# top_k: 256
# max_chars_per_chunk: 800
# max_total_chars: 8000
# - name: list_knowledge_bases
# group: knowledge
# use: deerflow.community.ragflow.tools:list_knowledge_bases_tool
# Optional tenant-shared, read-only LightRAG retrieval; alternative
# provider for the same knowledge_search tool (duplicate names keep the
# first entry, so configure exactly one). Requires LightRAG v1.4.9+. Put
# LIGHTRAG_API_KEY in `secrets` when the server enables API-key auth;
# omit `api_key` only for unauthenticated trusted-network deployments.
# Searches the single indexed workspace through the data-retrieval
# endpoint with no LLM generation inside LightRAG. Internal chunk and
# reference identifiers stay hidden from the Agent.
# - name: knowledge_search
# group: knowledge
# use: deerflow.community.lightrag.tools:knowledge_search_tool
# base_url: http://lightrag:9621
# api_key: $LIGHTRAG_API_KEY
# mode: mix
# timeout: 30
# top_k: 60
# chunk_top_k: 8 # Optional; omit to use the server default
# max_chars_per_chunk: 800
# max_total_chars: 8000
- name: web_search
group: web
use: deerflow.community.ddg_search.tools:web_search_tool
max_results: 5
- name: web_fetch
group: web
use: deerflow.community.jina_ai.tools:web_fetch_tool
timeout: 10
- name: image_search
group: web
use: deerflow.community.image_search.tools:image_search_tool
max_results: 5
- name: ls
group: file:read
use: deerflow.sandbox.tools:ls_tool
- name: read_file
group: file:read
use: deerflow.sandbox.tools:read_file_tool
- name: glob
group: file:read
use: deerflow.sandbox.tools:glob_tool
max_results: 200
- name: grep
group: file:read
use: deerflow.sandbox.tools:grep_tool
max_results: 100
- name: write_file
group: file:write
use: deerflow.sandbox.tools:write_file_tool
- name: str_replace
group: file:write
use: deerflow.sandbox.tools:str_replace_tool
- name: bash
group: bash
use: deerflow.sandbox.tools:bash_tool
# -- Initial DeerFlow extensions_config.json content (MCP servers + skill
# state). The Gateway copies this ConfigMap seed into its writable home
# volume only when no runtime file exists. With persistence enabled, API
# updates and the original seed survive pod replacement and Helm upgrades;
# remove the runtime file explicitly if a later seed should replace it.
extensionsConfig: |
{"mcpServers":{},"skills":{}}