mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
* feat(scheduler): lifecycle rules, goal/limit parity and coded errors for scheduled tasks
- Migration 0031: nullable scheduled_tasks.unmet_streak_after_seq (internal
streak boundary) and stop_condition (the user's stop rule, own column).
- Stop rule is normalized (stop_rule.py) and appended only at launch; the
stored prompt never contains it. Wording adapts when scheduler.tool_enabled
is off.
- Willem 1: page-created tasks get goal evaluation, <standing_notes>, the
previous-run reference and own-schedule stop; request_stop and the
scheduled capability no longer require an origin conversation.
- Repository: single reactivation choke point raising
ScheduledTaskLimitsExhausted; streak boundary moves on goal/prompt/stop
condition/note changes (not on resume); goal-check failures neither count
nor reset the streak; pause of a finished task returns "finished"; trials
on a paused task keep the host pause marker.
- New repository reads: automatic_runs_used_for, active_run_status_for,
list_manageable_from_thread, thread_relation/thread_run on thread tasks,
task_ids_for_thread, run_number, and run_number/total_tokens/summary on
run history rows.
- REST: goal/max_runs/end_at/stop_condition on create and PATCH (null
clears, naive end_at = task timezone), resume recomputes from now with an
optional renewal body, once_time_passed, task_finished, limits_exhausted,
scheduler_not_running on create, trigger returns outcome/existing/thread_id,
task responses add automatic_runs_used and active_run_status.
- Coded errors {code, message, params} for routes, shared validation and the
chat capability (scheduled_task_errors.py), pinned by
contracts/scheduled_task_errors_contract.json; goal notes contract v2 with
check_failure_codes, scheduled_origin_key and host_run_errors (host_notes.py).
- /api/features adds scheduled_tasks {available, running, tool_enabled,
min_interval_seconds}; ScheduledTaskService.is_running.
- Docs: six README scheduler sections, API.md scheduled tasks section
(breaking error shape), CONFIGURATION.md, AGENTS notes, CHANGELOGs.
- Tests: migration 0031, stop rule, error contract, run listing, lifecycle
over HTTP, repository, service and capability cases; updated tests for the
new error shape, finished-task pause and head revision.
* feat(scheduler): conversation update/resume, run-conversation authority and readable launches
- schedule_task: update and resume actions, stop_condition and clear_fields
parameters, plain parameter types (value rules come back as coded results),
rewritten docstrings for schedule_task and stop_scheduled_task.
- Capability: update/resume share the REST validation and error codes;
interactive turns in a task's run conversation may manage that task;
scheduled runs keep only own-stop; grants require a running scheduler.
- Results: compact JSON-safe task views with local times, display card/text,
last run in list, timezone_source/browser_timezone, now_local; no ISO, user,
lease or origin fields; stop_instructions removed.
- Timezone: explicit > browser (context.client_timezone, validated in
start_run and never forwarded to the run config) > not needed for intervals
and offset run_at > timezone_required.
- Trial gate accepts natural direct requests after acknowledgements and
particles; trial results report thread_id and existing.
- Scheduled launches: stable message id scheduled-<task_run_id>, server-owned
deerflow_scheduled_origin metadata with the user-language parts, run thread
title for fresh-thread tasks.
- Worker records goal continuations in the verdict; lead prompt language rule
covers stored task text and scheduled runs.
- Docs: six READMEs, CONFIGURATION, API, runtime/tools AGENTS, CHANGELOGs.
* feat(frontend): scheduled-task core types, formatting, errors and glossary
- throwGatewayApiError now throws GatewayApiError (status, code, params,
rawMessage); message stays the string detail or detail.message
- scheduled-task types gain stop_condition, automatic_runs_used,
active_run_status, origin/notes, run_number/total_tokens/summary, thread
relation, trigger result, renewal and scheduled origin; isTaskBusy and
hasActiveRun helpers
- api: fetchScheduledTask, resume with optional renewal body, typed trigger
result and update payload with null clears
- hooks: polling at 3 s while a run is active (list, thread list, run
history), useScheduledTask with live gating and final 404, useInView,
localized error toasts; run history refreshes tasks when a run finishes
- new pure modules: errors.ts (contract code to localized copy), format.ts
(relative task times, viewer time, display zone), status.ts, actions.ts,
run-error.ts; describeTaskSchedule in cron.ts; goal-outcome adds unchecked
goal checks, describeTaskOutcome and parseStopRunId
- features: scheduled_tasks block with defaults when missing
- buildRunContext sends client_timezone
- titleOfThread takes a localized untitled label; breadcrumb names scheduled
tasks and agents; reference chips use the localized untitled label
- schedule input: shared browserTimeZone, labelled selects, aria-pressed
type toggles, interval minimum from the hint template
- i18n: all PR1 scheduled-task keys in en-US and zh-CN, glossary fixes,
recipe instructions moved into i18n
- e2e mocks follow the new task, trigger, resume, thread-task and features
shapes; expectNoRawIdentifiers helpers for e2e and DOM tests
- tests for errors, format, cron, status, actions, goal outcome, run errors,
hooks, run context, thread titles and features; scheduled e2e copy updated
* feat(frontend): list-first scheduled tasks page with outcomes and editable goals
- page.tsx is now a thin composition of components/workspace/scheduled-tasks:
status tabs (All/Active/Paused/Finished) and search over the task list,
?task_id= deep link with router.replace on selection, ?thread_id= chip
with Show all, empty state, scheduler-off notice and unavailable state
- task list rows show the schedule in words and one status line (next run,
running now, paused by agent, auto-paused, finished by its cap)
- task detail: status badge, actions from availableActions with reasons,
More actions menu (Duplicate, Copy task ID, Delete), outcome notice,
Runs / Stops when / Each run's goal / Does / Notes from chat / History,
origin chat link; success toasts for every action
- run rows: run number or Trial run, summary, goal and assumption badges,
continuations, tokens, Open chat link; raw errors and the evaluator
reason only behind Details
- create/edit/duplicate dialog with goal, stop condition, safety caps and
advanced agent/context options; PATCH sends only changed fields, null
clears a cap
- renew dialog raises or removes caps and resumes in one request; a
limits_exhausted Resume opens it
- removed i18n keys the old page used; added feedback.resumedNoTime
- recorded en-3 and zh-1 page fixtures; scheduled e2e specs rewritten for
the new page; DOM tests for the detail and the form dialog
- CHANGELOG entries and the frontend AGENTS.md scheduled-task note
* feat(frontend): live schedule card, run conversation provenance and header entry
- schedule_task results (create, update, pause, resume, trial, delete) become
an assistant:scheduled-task message group rendered as a live card: schedule,
next run, stop condition with the safety cap, goal, where results go, Run
once now, Pause/Resume and Open task; the card polls the task only while it
is on screen and shows deleted, scheduler-off and limits-exhausted states
- one card per task per assistant turn; the card group is not a turn boundary
for usage, branching, editing, outline or skill attribution
- a human message with deerflow_scheduled_origin stays a human group with
scheduledOrigin and renders as a "Scheduled run / Trial run" block with the
task instructions, stop condition and notes collapsed; the launched text is
never shown and the message is not editable
- readable step labels for schedule_task actions and stop_scheduled_task
- ThreadScheduledTasksButton replaces the header link: hidden without tasks,
shows the count, links to the task (or the chat filter), and a run's chat
links to its task
- recorded en-3 / zh-1 chat and run-thread fixtures; chat-card and run-thread
e2e specs; unit and DOM tests for parsing, grouping, turn boundaries, card,
run block, header button, step labels and reconnect hydration
- i18n: header.countLabelOne added, unused card.next removed; CHANGELOG and
frontend AGENTS.md notes
* fix(scheduler): address verification findings
Move the scheduled-task lifecycle guidance from backend/AGENTS.md into the persistence guide and leave a one-line pointer, so the effective AGENTS.md chains for agents/middlewares, sandbox and subagents stay under the 98304-byte hard limit enforced by scripts/check_agent_guidance.py in lint-check.
* fix(scheduler): address review findings
* fix(gateway): no-transform on SSE responses
All four text/event-stream responses (thread run create stream, join,
existing-run GET/POST stream, stateless create stream) now take their
headers from app/gateway/sse_headers.py and send
Cache-Control: no-cache, no-transform, so compressing proxies such as
Next's rewrite proxy stop buffering streams. Content-Location and
X-Accel-Buffering are unchanged; scheduled and MCP-notification runs keep
stream_mode=None.
Tests pin the headers on every SSE route and fail on a literal no-cache
header left in routers. Docs: API.md SSE headers note, CHANGELOGs.
* feat(scheduler): per-owner run cap and fair queue selection
New scheduler.max_concurrent_runs_per_user (default 2, 0 = off, effective
min(cap, max_concurrent_runs)) caps launching/running scheduled runs per task
owner. claim_queued_run checks it right after the global count under the same
budget lock, so it is atomic across workers and Pods. list_queued_runs ranks
same-thread FIFO heads per owner with ROW_NUMBER() OVER (PARTITION BY owner)
and orders the batch by that rank, so the drain is owner-fair even with the
cap off; owners at their cap drop out before the batch limit. Orphan rows
(task deleted while queued) are never held back by the cap.
Gateway lifespan passes the startup value; the service constructor default
stays 0. Docs: config.example.yaml (config_version 54), CONFIGURATION.md,
the six READMEs, backend AGENTS.md, CHANGELOGs (behavior change flagged).
The live-task quota is untouched.
* feat(scheduler): durable lifecycle events for the originating chat
When a chat-created schedule is paused by the agent, auto-paused or finishes,
finalization now reports task_stopped / task_paused / task_finished, only on a
live -> paused/finished transition. The new finish_task_at_end_condition
replaces the three inline "mark completed" blocks (admission rejected as ended,
claim-time skip, complete_if_ended), so every site emits exactly once.
The scheduler's finalization observer is always installed. It writes
scheduled_task_events rows for the originating chat in the same transaction as
the state change, deduplicated on (task_id, anchor, event), with a title
snapshot, stop condition, run number, last run outcome and the chat's newest
run as placement anchor. Lookups fall back on bad stored data; only database
errors propagate. Manual trials drop only run_* events. The IM step keeps
PR1's selection until B4.
New GET /api/threads/{thread_id}/scheduled-task-events (owner check, caller's
rows, oldest first); rows survive task deletion and go with the chat.
Migration 0032_activity_and_task_events adds the events table plus the
run-origin column/index and read-state tables used later, each object guarded
on its own. Contract scheduled_goal_notes v3 pins the lifecycle vocabulary.
Docs: API.md, migrations chain, backend AGENTS.md, six READMEs, CHANGELOGs.
* feat(scheduler): honest, localized and self-contained IM notices
Every scheduled-task IM notice is now enqueued by the finalization observer
in the transaction that records the outcome, one message per occurrence
(task_stopped > task_paused > task_finished > run_failed > run_unmet >
run_completed; a once task sends only its run event). The merged auto-pause
replaces the separate unmet + pause messages, agent stops and finishes now
notify, and recovery-finalized runs notify exactly once. Idle finishes use a
deterministic idle: key derived from the lifecycle anchor. The old
non-transactional enqueue in handle_run_completion is removed.
New app/channels/capabilities.py holds CHANNEL_CAPABILITIES with a
proactive_notifications flag (adds the missing qq entry); only push-capable
providers (WeCom) get rows, /api/channels/providers returns the flag, and the
worker ends legacy rows for other providers as terminal failed.
New app/scheduler/notification_text.py renders en-US/zh-CN text: title, what
happened (plus the last run's outcome), the tasks page's one-line run summary
(redacted) when the agent replied, and a closing line. No IDs, no links, no
public_base_url. Locale is the owner's new `locale` preference, else the new
channel_connections.notification_locale (default en-US).
Docs: config.example.yaml (config_version 55), CONFIGURATION.md, API.md,
channels AGENTS.md, six READMEs, CHANGELOGs (behavior change flagged).
* feat(gateway): thread origin, activity feed and read markers for server-created threads
Runs started by the server for a user (scheduler, IM channels, GitHub
agents, extension handles, MCP notifications) now carry a server-owned
metadata.deerflow_origin {kind, provider?, namespace?} (new harness module
deerflow.runtime.run_origin, pinned by contracts/thread_origin_contract.json).
It is stamped only from request.state.run_origin or by the internal channel
caller; run admission drops client copies from both metadata and
config.metadata, and thread create/patch strip it. RunRepository derives
runs.origin_kind on put and on create_thread_operation_atomic (agent runs
only), and gains latest_change().
New ThreadReadRepository keeps a per-user, never-decreasing read position
(thread_read_markers) and a per-user read clock (thread_read_versions).
POST /api/threads/{id}/read marks a thread read, thread search items carry
unread, and thread delete removes the markers. GET /api/thread-activity pages
the caller's run changes over the run-change clock, returns only threads
changed by server-originated runs plus read_version, and 503s without SQL;
/api/features reports thread_activity.available.
Docs: API.md, gateway AGENTS.md (condensed to stay under its budget),
CHANGELOGs.
* fix(scheduler): adapt PR2 to PR1 follow-ups
PR1 moved the scheduled-task lifecycle rules out of backend/AGENTS.md into
the persistence guide and grew the sandbox AGENTS.md chain to within 267
bytes of its hard limit. The per-owner queue-fairness detail now lives in
the persistence guide next to the lifecycle rules (backend/AGENTS.md keeps
the cap and a link), so check_agent_guidance passes again.
PR1's goal-outcome unit test pinned scheduled_goal_notes_contract version 2;
PR2 bumps it to 3 (lifecycle vocabulary only, the v2 keys are unchanged).
* fix(scheduled-tasks): polish from live acceptance
- zh schedule wording: an interval of one unit reads 每分钟 / 每小时 / 每秒,
and a saved interval of whole days reads 每天 / 每 2 天 (Every day / Every 2
days) instead of 每 1 分钟 or 每 24 小时; several units keep the number
- zh spacing: 已于{time} 满足 and 在{time} 的运行中 now leave a space between
the interpolated time and the Chinese after it; a copy test guards every
scheduled-task zh string against a placeholder glued to Chinese
- run summary: when the first line of the final reply only leads in to a list
(ends with ":" or ":"), the list items follow it as plain text (bullets,
numbering, checkboxes, bold, code and links stripped), joined with ";" for
CJK text and "; " otherwise, within the 160-character cap
- a schedule_task result (create, update, pause, resume, trial, delete) that
arrives in a chat invalidates that chat's task list, the tasks list and the
named task, so the header button and cards update without waiting for the
idle poll; results already in the loaded history do not refetch
- e2e and unit fixtures are now the live acceptance recordings (DeepSeek
Flash, demo data, fictional names): live-weekday.json (create, trial, edit,
pause, resume in one zh chat) and live-minute.json (per-minute chat, run 1
and run 2, paused by the agent), fetched with GET from the demo Gateway and
trimmed; the hand-shaped en-3 / zh-1 fixtures are removed and the chat-card,
run-thread and tasks-page specs run in both locales over the recorded data
* docs(changelog): reference #6378 for the scheduled-tasks entries
* docs(agents): shorten the scheduled-task pointer to stay under the guidance limit
* test(scheduled-tasks): pin the live takes to their recorded time zone
The live fixtures were recorded in Asia/Shanghai, and an interval task
reads in the viewer's zone, so the 16:48 assertions failed on UTC CI
runners. Pin the browser zone in the two live-take e2e specs and the
zh live-take unit test.
* feat(frontend): thread origin, activity polling, read markers and task events core
Frontend core for server-created threads and schedule lifecycle events
(PR2 F1); the sidebar markers (F2) and the chat event line (F3) build on it.
- core/threads/origin.ts: threadOriginOf reads metadata.deerflow_origin,
then channel_source (github -> GitHub origin), then a legacy
scheduled_task_id; key and kinds pinned to
contracts/thread_origin_contract.json. labelOfThreadOrigin and the now
exported labelOfChannelProvider(provider, t) use the new localized
threads.origin.providers names (飞书, 企业微信, ...), with the English map
(gaining GitHub) only as the fallback.
- core/threads/activity.ts: useThreadActivity polls /api/thread-activity
every 15 s (focus refetch, never in the background, off unless
features.thread_activity.available). The first response only seeds the
cursor; server-origin threads, a truncated page or a higher read_version
invalidate the thread lists, a scheduled thread also ["scheduled-tasks"];
an invalid cursor re-seeds. useMarkThreadRead debounces POST /read by 1 s,
skips a thread whose cached unread is false, patches the search and
infinite caches optimistically and stores the returned read_version so a
tab's own read does not refetch its lists. AgentThread gains unread.
- core/scheduled-tasks/events.ts: ScheduledTaskEvent, the events query
(["scheduled-tasks","thread-events",id], one fetch per saved chat, not
gated on the task list so a deleted task's lines stay, refetched when a
task of the chat changes status or finishes a run), describeTaskEvent
(en/zh copy, title and condition as segments, last-run suffix, See that
run / Open task) and placeTaskEvents (end of the after_run_id turn, else
the tail, ordered by created_at); lifecycle names pinned to contract v3.
- features: thread_activity (default unavailable); channels:
proactive_notifications on providers.
- Locale sync: the I18nProvider reports the UI language and a signed-in
preference session saves it as the new `locale` preference when the
account's differs (and on every switch), so IM notices follow the UI
language; nothing is written with auth disabled.
- i18n: threads.* (unread, origin labels, provider names),
scheduledTasks.events.*, history.waitingForSlot (now shown for queued run
rows), channels.scheduledUpdates.*.
- e2e mocks: thread activity feed and controller, POST /read, the events
route, providers with proactive_notifications, search unread, features
thread_activity (off by default). Preference e2e mocks return the UI
locale so they keep asserting only their own edits.
- Docs: frontend AGENTS.md, CHANGELOGs.
* fix(scheduled-tasks): keep untouched end times and drop a chat filter the new task is not in
- The edit, duplicate and renew dialogs keep a stored end time's instant
while its field still shows it. A minute-precision wall value cannot
keep seconds or name the later 01:30 of a DST fall-back, so a
title-only edit moved the cap an hour earlier, and raising the run
limit re-sent the end time without its seconds.
- Creating or duplicating a task from a chat's task view drops the chat
filter when the new task does not belong to that chat, instead of
showing "This task isn't one of this chat's tasks".
* feat(frontend): origin markers and unread state in the thread lists
Server-created threads (PR2 F2) now say who created them and whether they
are new, in the sidebar and on the Chats page, and reach an open sidebar
without a reload.
- ThreadOriginIcon (thread-channel-source.tsx): clock for a scheduled run,
the provider mark for IM threads, the GitHub mark (new in
ChannelProviderIcon) for GitHub threads, a puzzle piece for extensions;
the localized "Scheduled run" / "From Feishu" / "From GitHub" / "From an
extension" label is its accessible name and tooltip. ThreadChannelIcon
stays exported as a wrapper, and the provider chip tooltip is localized
(no more "Feishu channel").
- Unread dot after the title (aria-hidden dot plus sr-only "Unread"); the
row aria-label becomes "{title}, unread" / "{title},未读". Never shown for
the open thread.
- WorkspaceSidebar mounts useThreadActivity once.
- useMarkOpenThreadRead (components/workspace/chats): the default and
custom-agent chat pages mark the thread read once its metadata has
loaded, when its cached unread flips to true while open, when the tab
becomes visible, and from onFinish (a send or a joined run ended). Each
trigger goes through useMarkThreadRead, so it is debounced and a no-op
while the cached unread is false.
- Tests: thread-origin-icon and recent-chat-list DOM tests (en/zh, active
row, no raw identifiers), use-mark-open-thread-read DOM tests,
e2e thread-activity.spec.ts (new scheduled run appears unread within one
fake-clock poll without a reload, opening it posts /read and clears the
dot, a read on another device clears it, IM and GitHub markers, zh
labels, the Chats page, no polling when unavailable);
thread-history.spec.ts now finds the localized origin icon.
- Docs: frontend AGENTS.md, CHANGELOGs.
* feat(frontend): schedule event line in the originating chat and channel notice support
The chat that created a schedule now says when the schedule paused or
ended, and the channel cards say which apps receive scheduled task updates
(PR2 F3).
- ScheduledTaskEventLine (mockup m2, marker 4): a dashed, muted row with
the describeTaskEvent text (title in <strong>, stop condition clamped to
one line with the full text in its tooltip, last-run suffix), the time
via formatTaskTime and a right-aligned "See that run" (run chat) or
"Open task" link. PauseCircle for paused, CircleCheck for finished and
once-done, CircleX for once-failed. role="note", aria-label "Scheduled
task update" / "定时任务通知", data-task-id and data-event-id only.
- MessageList takes scheduledTaskEvents and renders placeTaskEvents output
through a new VirtualMessageList.renderAfterGroup, so each line sits at
the end of its after_run_id turn inside the group's measured row (also
when the list virtualizes); lines with nowhere to go render at the tail.
- Both chat pages load useThreadScheduledTaskEvents (not gated on the task
list, so the lines stay after a task is deleted). The custom-agent page
passes its agent so "See that run" opens the run on that agent's route.
- ChannelScheduledUpdates: "Scheduled task updates: sent here" / "... not
available for this app yet" (定时任务通知:会发送到这里 / 此应用暂不支持)
on the Settings provider cards and under each sidebar channel row; nothing
when an older backend does not report proactive_notifications.
- e2e mock controller: setScheduledTasks.
- Tests: scheduled-task-event-line and channel-scheduled-updates DOM tests
(every kind in en and zh, hrefs, clamp, no raw identifiers, Chinese
title), MessageList placement DOM tests; e2e
scheduled-task-chat-events.spec.ts (the line appears with the card's
"Paused by agent" without a reload, See that run opens the run chat,
anchored vs tail placement, two events in time order, the line survives
task deletion and Open task shows the task is gone; en-3 and zh-1) and
channels.spec.ts (WeCom sent here, Feishu not yet; en and zh).
- Docs: frontend AGENTS.md, CHANGELOGs.
* fix(scheduled-tasks): tie pause notices to the pause they describe
- Paused by agent links and times only the run named in last_error. When
that run is not loaded, the notice shows no link and the task's own time
instead of an earlier run that also asked to stop.
- The outcome notice reads the latest history page whichever page is
shown, so browsing older runs no longer swaps the auto-pause reason and
"Open latest run" to an earlier miss. It shares that page's request.
* fix(scheduler): address PR2 review findings
- Event lines: hold an event whose anchor run is on an unloaded history
page instead of placing it at the bottom (hasMoreHistory); tail fallback
only for fully loaded (branched/pruned) history or no anchor.
- Idle-finish anchor follows its reason: end:<iso> only for an end_at
finish, seq:<n> otherwise, so a later end-time finish is not deduplicated
away by an earlier max_runs finish.
- Observer takes FOR SHARE on the originating threads_meta row, so a racing
thread delete cannot leave an orphan event line.
- One taskPagePath (core), re-exported by the card.
- Notification locales derived from NotificationLocale; /preferences uses it.
- IM notices drop generic reasons (goal_not_met_yet, unknown) instead of
repeating the sentence.
- Localized provider names on Settings cards, sidebar rows and setup dialog.
- zh event lines: no space after a Chinese title (Latin-aware gap).
- Unread row label keeps the origin marker; chats page keeps the time as a
description.
- Sidebar shows the scheduled-updates line only for connected apps;
Settings says "available after you connect" for unconnected push apps.
- Event line keeps a real space before the time.
* fix(scheduled-tasks): three lifecycle gaps found while re-checking review classes
- The edit form compares a cron in the form its schedule input emits, so an
untouched "0 09 * * 0,6" is not re-sent as "0 9 * * 6,0" and a title-only
edit no longer re-arms or refuses a finished task.
- Chat resume asks for a timezone, as update does, when a naive end_at would
land in the UTC placeholder of a zone-free task.
- Re-arming a task clears a host pause marker, as resume does, so a later
manual pause no longer reads as "Paused by agent".
* test(scheduler): consolidate PR2 backend test setup
- Fold the repeated task, origin-chat and observer setup of the
lifecycle-event repository tests into one lifecycle_task builder.
- Build the per-owner-cap services through one _capped_service builder
that keeps every cap explicit at the call site.
- Table the five SSE route header checks into one parametrized test with
the same routes, bodies and Content-Location expectations.
- Reuse bind_wecom and hoist repeated local imports in the event tests.
Every case and assertion is kept; collected cases are unchanged.
* test(frontend): consolidate PR2 thread activity and origin test setup
Extract shared builders and helpers in the frontend tests PR2 adds, with
every case, input and assertion kept:
- activity.test.ts: seededClient/apply helpers and one LIST_KEYS constant
replace the per-test seed, spy and apply boilerplate.
- mark-read.dom.test.tsx: newClient() and advance() replace the repeated
QueryClient wrapper and fake-timer act blocks.
- origin.test.ts: the English and Chinese label checks become tables; the
no-English-name check now runs on every Chinese row.
- thread-activity.spec.ts: a scheduledRun() builder replaces four inline
copies of the scheduled run thread.
- channels.spec.ts: the PR2 block uses setLocaleCookie and one card helper.
Collected cases are unchanged (unit 211, e2e 60); no test was removed.
* fix(scheduled-tasks): keep the notice's latest-runs query from refreshing older pages
useLatestScheduledTaskRuns kept React Query's default refetch on mount,
focus and reconnect, so going offline and back while an older history page
was shown fetched the latest page again. Refreshing stays with the history,
which refreshes the latest page only while it is shown.
* fix(scheduler): two gaps found in live acceptance
- A fresh scheduled run thread is named when it launches. The worker
copied the title to the thread list only when the run ended, so the
sidebar, which now shows the new run thread at once, listed it as
untitled while it ran.
- A task capped at one run finishes with "its one run is done" /
"唯一一次运行已完成" in the chat event line and the IM notice, instead
of "all 1 runs are done".
* test(scheduled-tasks): let the viewer's time carry its own day label
Between 16:00 and 24:00 UTC, UTC and Asia/Shanghai are on different days,
so the run prompt shows "Tomorrow 01:00 · Today 09:00 your time" and the
fixed "01:00 · 09:00 your time" assertion failed depending on the hour.
* test(scheduler): expect the one-run finish copy in service notices
* fix(gateway): create a scheduled run thread with its title
Pre-setting the name after start_run raced the thread row, which the run
worker creates asynchronously: live, one of two run threads still reached
the sidebar untitled. Run admission now creates a new thread with the title
its run input carries, which the worker would copy only at run end.
* docs(changelog): reference #6393 for the scheduled-task signal entries
* fix(scheduled-tasks): route event-line run links by the agent that ran them
The originating chat's agent was used for "See that run", but a task's
agent can change after the chat created it, so the link could open the run
chat on another agent's route. Lifecycle events now record run_agent_name,
the task's agent when the run finished (edits are refused while a run is
active), and the line routes by it.
* fix(scheduled-tasks): load up to the route's maximum of chat event lines
The chat loads its lifecycle events once, and the route's default of 50
would silently drop the oldest lines of a chat with many schedules. Ask
for the maximum, 200.
* fix(threads): keep activity reads and the first cursor in step with the lists
- Accepting the first activity cursor refreshes the thread lists once. The
lists load on their own, possibly before a run the seed already counts,
and later polls start after it, so such a thread never appeared.
- A local read whose returned read clock jumps past that one read also
covers reads on other devices; the lists are refreshed instead of the
clock silently skipping those threads' stale unread dots.
- An invalid cursor re-seeds through the same path, so it refreshes once.
* docs(agents): bring the middlewares guidance chain back under the hard limit
#6378 and #6363 each fit the budget, but together the middlewares AGENTS.md
chain on main is 98,425 bytes, 121 over the 98,304 hard limit, so Lint
Check fails on main. Move the scheduled-task recovery, launch-update and
queue-timeout rules from backend/AGENTS.md into the persistence guide's
lifecycle section, which backend/AGENTS.md already points to. The chain is
now 98,067 bytes.
* fix(helm): bump the chart's config_version to 55
config.example.yaml moved to 55 in this PR, so the Helm chart's
validate-chart drift check failed. The chart's embedded config needs no
field changes: the new scheduler and channel keys have defaults.
---------
Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
3462 lines
173 KiB
YAML
3462 lines
173 KiB
YAML
# Configuration for the DeerFlow application
|
|
#
|
|
# Guidelines:
|
|
# - Copy this file to `config.yaml` and customize it for your environment
|
|
# - The default path of this configuration file is `config.yaml` in the project root.
|
|
# You can set `DEER_FLOW_PROJECT_ROOT` to define that root explicitly, or use
|
|
# `DEER_FLOW_CONFIG_PATH` to point at a specific config file.
|
|
# - Runtime state defaults to `.deer-flow` under the project root. Override it
|
|
# with `DEER_FLOW_HOME` when you need a different writable data directory.
|
|
# - Set `DEER_FLOW_DATE_TIMEZONE` to an IANA timezone name (e.g. `Asia/Shanghai`)
|
|
# when the conversation date injected into agents should follow a zone other
|
|
# than the server's local timezone. It is read at runtime by the date-context
|
|
# middlewares (not a config-schema field), so it can be set on a container
|
|
# without mounting a `config.yaml`.
|
|
# - Environment variables are available for all field values. Example: `api_key: $OPENAI_API_KEY`
|
|
# - The `use` path is a string that looks like "package_name.sub_package_name.module_name:class_name/variable_name".
|
|
|
|
# ============================================================================
|
|
# Config Version (used to detect outdated config files)
|
|
# ============================================================================
|
|
# Bump this number when the config schema changes.
|
|
# Run `make config-upgrade` to merge new fields into your local config.yaml.
|
|
config_version: 55
|
|
|
|
# Optional operator-owned literal prompt extensions (see CONFIGURATION.md):
|
|
# lead_prompt_overlay:
|
|
# prepend: "Apply our organization-wide writing conventions."
|
|
# append: "End reports with a concise decision summary."
|
|
# Subagents: subagents.agents.<name>.prompt_overlay.{prepend,append}
|
|
# DeerMem: memory.backend_config.{prompt_prepend,prompt_append}
|
|
|
|
# Optional per-model request pacing (inside a models[] entry):
|
|
# request_admission:
|
|
# requests_per_minute: 60
|
|
# group: shared-provider-account # optional; defaults to the model config name
|
|
# max_wait_seconds: 300
|
|
# max_queue_size: 256
|
|
# Off unless configured. Shared groups require identical settings; restart after
|
|
# changes. Process-local RPM only, not TPM or a cluster-wide quota. See the
|
|
# "Model request admission" section in backend/docs/CONFIGURATION.md.
|
|
|
|
# ============================================================================
|
|
# Logging
|
|
# ============================================================================
|
|
# Log level for deerflow modules (debug/info/warning/error)
|
|
log_level: info
|
|
|
|
# Trace ids are always issued and always returned in the `X-Trace-Id` response
|
|
# header; this block controls log output only — whether records carry a
|
|
# `trace_id` field, and in which format. Off by default because enabling it
|
|
# changes the log format. Restart required (see reload_boundary.py).
|
|
logging:
|
|
enhance:
|
|
enabled: false
|
|
format: text
|
|
|
|
# ============================================================================
|
|
# Agent Extensions
|
|
# ============================================================================
|
|
# Optional AgentMiddleware classes loaded into the lead and subagent runtime
|
|
# middleware chains after built-in runtime middlewares, but before the
|
|
# safety/clarification tail. Missing packages, invalid classes, and broken
|
|
# modules fail at agent creation with an actionable import error; constructor
|
|
# errors fail at agent creation with the underlying exception.
|
|
# The same list applies to both lead and subagent runtimes; lead-only vs
|
|
# subagent-only configuration is not expressible yet. Each entry is a class
|
|
# path or `{class, kwargs}` for constructor arguments. kwargs values must be
|
|
# JSON types (object, array, string, number, boolean, or null); YAML dates
|
|
# and timestamps are coerced to ISO strings so they match JSON. Treat these
|
|
# files as trusted operator config because middleware classes execute code.
|
|
# Uncomment this block to define middlewares in config.yaml. Leaving it commented
|
|
# lets extensions_config.json remain the source of truth for this fixed-slot
|
|
# (not deprecated) middleware list. Packaged plugins use the `plugins:` block below.
|
|
# extensions:
|
|
# middlewares:
|
|
# - my_company.deerflow_middlewares:DomainGuardMiddleware
|
|
# - class: my_company.deerflow_middlewares:LatencyStampingMiddleware
|
|
# kwargs:
|
|
# header: X-DeerFlow-Latency
|
|
|
|
# ============================================================================
|
|
# Tracing / Observability (Monocle)
|
|
# ============================================================================
|
|
# Optional agent tracing via Monocle. Configured through environment variables
|
|
# (MONOCLE_TRACING, MONOCLE_EXPORTERS, OKAHU_API_KEY — like LangSmith/Langfuse),
|
|
# not config.yaml keys, and OFF by default. See README.md → "Monocle Tracing"
|
|
# for setup, what each exporter captures, and where the trace data goes.
|
|
|
|
# ============================================================================
|
|
# Token Usage
|
|
# ============================================================================
|
|
# Enable token usage collection and display.
|
|
# When enabled, DeerFlow records input/output/total tokens per model call
|
|
# and shows usage metadata in the workspace UI when providers return it.
|
|
token_usage:
|
|
enabled: true
|
|
|
|
# ============================================================================
|
|
# Token Budget — Per-run token limits
|
|
# ============================================================================
|
|
# Prevents runaway API costs by enforcing hard token limits per run.
|
|
# When warn_threshold is crossed, the agent receives an in-context warning.
|
|
# When hard_stop_threshold is crossed, tool_calls are stripped and the agent
|
|
# is forced to produce a final answer immediately.
|
|
token_budget:
|
|
enabled: false # Set to true to activate budget enforcement
|
|
max_tokens: 200000 # Total token limit (input + output) per run
|
|
max_input_tokens: null # Optional separate input-only limit
|
|
max_output_tokens: null # Optional separate output-only limit
|
|
warn_threshold: 0.8 # Warn at 80% of the budget
|
|
hard_stop_threshold: 1.0 # Force stop at 100% of the budget
|
|
|
|
# ============================================================================
|
|
# Recursion Limits
|
|
# ============================================================================
|
|
# A run's recursion_limit caps the number of LangGraph super-steps (each is at
|
|
# least one LLM call). Clients may override the default per request; invalid or
|
|
# non-positive client values fall back to this configured default.
|
|
recursion_limit: 100
|
|
|
|
# The Gateway never trusts a configured or client-supplied value above this
|
|
# ceiling, preventing runaway API cost / DoS. Raise it only if you legitimately
|
|
# run very deeply nested subagent graphs.
|
|
max_recursion_limit: 1000
|
|
|
|
|
|
# ============================================================================
|
|
# Models Configuration
|
|
# ============================================================================
|
|
# Configure available LLM models for the agent to use.
|
|
#
|
|
# Two token fields look similar but mean different things:
|
|
# - `max_tokens` is the per-call OUTPUT cap passed to the provider.
|
|
# - `context_window` is a positive integer for the total context capacity
|
|
# (prompt + completion). It drives the real-time "% context used" indicator
|
|
# in the chat UI and feeds the model's langchain profile, which fraction-based
|
|
# summarization triggers (`summarization.trigger: [{type: fraction, ...}]`)
|
|
# resolve their thresholds from. Third-party OpenAI-compatible models carry no
|
|
# built-in profile, so without `context_window` a fraction trigger degrades
|
|
# (dropped with a warning) instead of crashing the agent build.
|
|
# Leave `context_window` unset if the provider limit is unknown; the percentage
|
|
# will not render. Verify configured values against the provider's model docs.
|
|
#
|
|
# Optional per-model pricing (powers the real-cost display on the workspace
|
|
# console). Add a `pricing` block to any model entry; use ONE currency across
|
|
# all models. Mixed currencies disable cost reporting to prevent invalid sums.
|
|
# Prices are per one million tokens.
|
|
#
|
|
# pricing:
|
|
# currency: CNY # ISO code shown in the console (CNY, USD, ...)
|
|
# input_per_million: 8.0 # price per 1M input tokens (cache miss)
|
|
# output_per_million: 32.0 # price per 1M output tokens
|
|
# input_cache_hit_per_million: 0.8 # price per 1M cache-hit input tokens
|
|
# # (optional; omit → hits billed at miss price)
|
|
|
|
models:
|
|
# Example: Volcengine (Doubao) model
|
|
# - name: doubao-seed-1.8
|
|
# display_name: Doubao-Seed-1.8
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: doubao-seed-1-8-251228
|
|
# api_base: https://ark.cn-beijing.volces.com/api/v3
|
|
# api_key: $VOLCENGINE_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# context_window: 262144 # Total prompt + completion capacity
|
|
# supports_thinking: true
|
|
# supports_vision: true
|
|
# supports_reasoning_effort: true
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: Volcengine Coding Plan (one key, multi-vendor gateway)
|
|
# The Coding Plan endpoint (/api/coding/v3) lets you access models from
|
|
# Doubao, GLM, DeepSeek, Kimi, and MiniMax with a single API key.
|
|
# Each model may differ in thinking/vision support - configure per-model.
|
|
#
|
|
# - name: glm-5.2-cp
|
|
# display_name: GLM-5.2 (Coding Plan)
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: glm-5.2
|
|
# api_base: https://ark.cn-beijing.volces.com/api/coding/v3
|
|
# api_key: $VOLCENGINE_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
# supports_reasoning_effort: true
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
#
|
|
# - name: deepseek-v4-pro-cp
|
|
# display_name: DeepSeek-V4-Pro (Coding Plan)
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: deepseek-v4-pro
|
|
# api_base: https://ark.cn-beijing.volces.com/api/coding/v3
|
|
# api_key: $VOLCENGINE_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
# supports_reasoning_effort: true
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Reasoning capability contract (optional, per model)
|
|
#
|
|
# A mapping-valued `reasoning:` block opts into this contract. The existing
|
|
# Ollama `reasoning: true` / `false` boolean, and the `low` / `medium` / `high`
|
|
# level string gpt-oss style models take, remain native provider options and
|
|
# are still forwarded to ChatOllama.
|
|
#
|
|
# `supports_thinking` / `supports_reasoning_effort` only say whether a knob
|
|
# exists. Models whose provider contract differs from DeerFlow's generic
|
|
# assumptions can declare a `reasoning:` block instead; the two booleans are
|
|
# then derived from it and every model-creation path (chat, summarization,
|
|
# title generation, subagents) enforces the same normalized policy:
|
|
#
|
|
# reasoning:
|
|
# thinking: optional # unsupported | optional | required
|
|
# on_disable_request: keep_enabled # required only: keep_enabled | reject
|
|
# dialect: auto # auto | openai_extra_body | anthropic |
|
|
# # vllm_chat_template | ollama | none
|
|
# history: clear # preserve | clear (omit for provider default)
|
|
# effort:
|
|
# values: [low, medium, high] # provider vocabulary, in display order
|
|
# default: medium # applied when the caller does not choose
|
|
# aliases: {minimal: low} # DeerFlow generic value -> provider value
|
|
# path: reasoning_effort # dotted identifier path (default)
|
|
#
|
|
# `required` thinking never receives a disable payload, unknown effort values
|
|
# never reach the provider (they map through `aliases` or fall back to
|
|
# `default`), and `dialect` lets the factory synthesize the on/off payload
|
|
# when `when_thinking_enabled` / `when_thinking_disabled` are absent
|
|
# (`auto` infers it from `when_thinking_enabled`). `default` also applies to
|
|
# callers that never choose an effort — summarization, title generation and
|
|
# subagents — so pick a level you are happy to pay for on every background
|
|
# call. Contradictory profiles fail at startup: `required` plus
|
|
# `when_thinking_disabled`, a `default` outside `values`, an effort value in
|
|
# the profile or in `when_thinking_*` templates that the contract rejects, a
|
|
# stale `reasoning_effort` beside a custom `effort.path`, or a `path` that
|
|
# would overwrite a whole mapping such as `extra_body`.
|
|
# Profiles without `reasoning:` behave exactly as before.
|
|
|
|
# Example: Z.AI GLM-5.3-Flash (required thinking, restricted effort)
|
|
#
|
|
# GLM-5.3-Flash cannot disable thinking and only accepts low/high/max effort.
|
|
# The contract keeps thinking on for every foreground and background call,
|
|
# maps DeerFlow's generic minimal/medium presets onto low/high, and
|
|
# serializes clear_thinking=true so the model does not require exact
|
|
# reasoning replay after summarization. `default: high` keeps background
|
|
# calls off the provider's deepest budget; `max` stays selectable in the UI.
|
|
#
|
|
# - name: glm-5.3-flash
|
|
# display_name: GLM-5.3-Flash
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: glm-5.3-flash
|
|
# api_base: https://api.z.ai/api/paas/v4
|
|
# api_key: $ZAI_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# temperature: 1.0
|
|
# top_p: 0.95
|
|
# max_tokens: 131072
|
|
# context_window: 1000000
|
|
# supports_vision: true
|
|
# stream_usage: false
|
|
# reasoning:
|
|
# thinking: required
|
|
# dialect: openai_extra_body
|
|
# history: clear
|
|
# effort:
|
|
# values: [low, high, max]
|
|
# default: high
|
|
# aliases:
|
|
# minimal: low
|
|
# medium: high
|
|
# extra_body:
|
|
# tool_stream: true
|
|
|
|
# Example: OpenAI model
|
|
# - name: gpt-4
|
|
# display_name: GPT-4
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: gpt-4
|
|
# api_key: $OPENAI_API_KEY # Use environment variable
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096 # Per-call output cap
|
|
# context_window: 128000 # Total prompt + completion capacity
|
|
# temperature: 0.7
|
|
# supports_vision: true # Enable vision support for view_image tool
|
|
|
|
# Example: OpenAI Responses API model
|
|
# - name: gpt-5-responses
|
|
# display_name: GPT-5 (Responses API)
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: gpt-5
|
|
# api_key: $OPENAI_API_KEY
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# use_responses_api: true
|
|
# output_version: responses/v1
|
|
# # Optional, forwarded to ChatOpenAI as-is (default false): send only the new
|
|
# # turn plus previous_response_id instead of replaying the full history. The
|
|
# # chained context is still billed as input tokens, and any client-side
|
|
# # history rewrite (e.g. blocked-write payload elision) is only honored when
|
|
# # the history is replayed.
|
|
# use_previous_response_id: false
|
|
# context_window: 400000
|
|
# supports_vision: true
|
|
|
|
# Example: Ollama (native provider — preserves thinking/reasoning content)
|
|
#
|
|
# IMPORTANT: Use langchain_ollama:ChatOllama instead of langchain_openai:ChatOpenAI
|
|
# for Ollama models. The OpenAI-compatible endpoint (/v1/chat/completions) does NOT
|
|
# return reasoning_content as a separate field — thinking content is either flattened
|
|
# into <think> tags or dropped entirely (ollama/ollama#15293). The native Ollama API
|
|
# (/api/chat) correctly separates thinking from response content.
|
|
#
|
|
# Install: cd backend && uv pip install 'deerflow-harness[ollama]'
|
|
#
|
|
# - name: qwen3-local
|
|
# display_name: Qwen3 32B (Ollama)
|
|
# use: langchain_ollama:ChatOllama
|
|
# model: qwen3:32b
|
|
# base_url: http://localhost:11434 # No /v1 suffix — uses native /api/chat
|
|
# num_predict: 8192
|
|
# temperature: 0.7
|
|
# reasoning: true # Passes think:true to Ollama native API
|
|
# context_window: 32768 # Match the context length served by Ollama
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
#
|
|
# - name: gemma4-local
|
|
# display_name: Gemma 4 27B (Ollama)
|
|
# use: langchain_ollama:ChatOllama
|
|
# model: gemma4:27b
|
|
# base_url: http://localhost:11434
|
|
# num_predict: 8192
|
|
# temperature: 0.7
|
|
# reasoning: true
|
|
# context_window: 32768
|
|
# supports_thinking: true
|
|
# supports_vision: true
|
|
#
|
|
# For Docker deployments, use host.docker.internal instead of localhost:
|
|
# base_url: http://host.docker.internal:11434
|
|
|
|
# Example: llama.cpp (OpenAI-compatible local server)
|
|
#
|
|
# llama-server exposes an OpenAI-compatible /v1/chat/completions endpoint,
|
|
# so use ChatOpenAI and point base_url at the server's /v1 root. --alias
|
|
# gives the served model a stable API name independent of the GGUF filename.
|
|
#
|
|
# Start llama.cpp, for example:
|
|
# llama-server -m /path/to/model.gguf --host 0.0.0.0 --port 8080 --alias qwen-local
|
|
#
|
|
# Tool calling depends on the model's chat template supporting tools. Current
|
|
# llama.cpp enables its Jinja template engine by default; if you override the
|
|
# template, use one that supports function/tool calls.
|
|
#
|
|
# - name: qwen-llamacpp
|
|
# display_name: Qwen (llama.cpp)
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: qwen-local
|
|
# api_key: no-key # Non-empty placeholder when llama-server auth is disabled
|
|
# base_url: http://localhost:8080/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# supports_vision: false
|
|
#
|
|
# If llama-server was started with --api-key, replace the placeholder with
|
|
# that key (prefer an environment variable). In Docker, localhost is the
|
|
# DeerFlow container itself; use an address reachable from the container,
|
|
# for example on Docker Desktop:
|
|
# base_url: http://host.docker.internal:8080/v1
|
|
|
|
# Example: Anthropic Claude model (with extended thinking)
|
|
# supports_thinking: true is required — without it, DeerFlow silently falls
|
|
# back to non-thinking mode even when the UI thinking toggle is on.
|
|
# budget_tokens is required by the Anthropic API when thinking.type=enabled
|
|
# (no server default; min 1024; must be less than max_tokens).
|
|
# - name: claude-sonnet-4
|
|
# display_name: Claude Sonnet 4
|
|
# use: langchain_anthropic:ChatAnthropic
|
|
# model: claude-sonnet-4-20250514
|
|
# api_key: $ANTHROPIC_API_KEY
|
|
# default_request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 16000 # Per-call output cap
|
|
# context_window: 200000 # Total prompt + completion capacity
|
|
# supports_vision: true
|
|
# supports_thinking: true
|
|
# when_thinking_enabled:
|
|
# thinking:
|
|
# type: enabled
|
|
# budget_tokens: 4096 # required; min 1024; must be < max_tokens
|
|
# when_thinking_disabled:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: Google Gemini model (native SDK, no thinking support)
|
|
# - name: gemini-2.5-pro
|
|
# display_name: Gemini 2.5 Pro
|
|
# use: langchain_google_genai:ChatGoogleGenerativeAI
|
|
# model: gemini-2.5-pro
|
|
# gemini_api_key: $GEMINI_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# context_window: 1048576
|
|
# supports_vision: true
|
|
|
|
# Example: Gemini model via Google's official OpenAI-compatible endpoint
|
|
# Use PatchedChatOpenAI so that tool-call thought_signature values on tool_calls
|
|
# are preserved across multi-turn tool-call conversations — required by the
|
|
# Gemini API when thinking is enabled. See:
|
|
# https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures
|
|
# Google accepts reasoning_effort, not extra_body.thinking. Gemini 3.1 Pro
|
|
# cannot disable thinking; dialect: none avoids emitting a thinking toggle.
|
|
# https://ai.google.dev/gemini-api/docs/openai#thinking
|
|
# - name: gemini-3.1-pro-preview
|
|
# display_name: Gemini 3.1 Pro (Thinking)
|
|
# use: deerflow.models.patched_openai:PatchedChatOpenAI
|
|
# model: gemini-3.1-pro-preview
|
|
# api_key: $GEMINI_API_KEY
|
|
# base_url: https://generativelanguage.googleapis.com/v1beta/openai/
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 16384
|
|
# context_window: 1048576
|
|
# supports_vision: true
|
|
# reasoning:
|
|
# thinking: required
|
|
# dialect: none
|
|
# effort:
|
|
# values: [minimal, low, medium, high]
|
|
|
|
# Example: Xiaomi MiMo model (with thinking support)
|
|
# MiMo thinking mode returns reasoning_content and requires that field to be
|
|
# replayed on historical assistant messages in multi-turn agent/tool-call
|
|
# conversations. Use PatchedChatMiMo instead of plain ChatOpenAI.
|
|
# Use https://api.xiaomimimo.com/v1 with pay-as-you-go `sk-...` keys.
|
|
# Use your Token Plan regional URL (for example
|
|
# https://token-plan-cn.xiaomimimo.com/v1) with Token Plan `tp-...` keys.
|
|
# PatchedChatMiMo is model-id agnostic; use it for every MiMo thinking model
|
|
# entry you configure (for example mimo-v2.5-pro, mimo-v2.5, mimo-v2-pro,
|
|
# mimo-v2-omni, or mimo-v2-flash), including models referenced by subagent
|
|
# model overrides.
|
|
# See: https://platform.xiaomimimo.com/docs/en-US/usage-guide/passing-back-reasoning_content
|
|
# - name: mimo-v2.5-pro
|
|
# display_name: MiMo V2.5 Pro
|
|
# use: deerflow.models.patched_mimo:PatchedChatMiMo
|
|
# model: mimo-v2.5-pro
|
|
# api_key: $MIMO_API_KEY
|
|
# base_url: https://api.xiaomimimo.com/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: DeepSeek V4 model (with thinking support)
|
|
# - name: deepseek-v4
|
|
# display_name: DeepSeek V4 (Thinking)
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: deepseek-v4-pro
|
|
# api_key: $DEEPSEEK_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# supports_thinking: true
|
|
# supports_vision: false # DeepSeek V4 does not support vision
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: Kimi K2.5 model
|
|
# - name: kimi-k2.5
|
|
# display_name: Kimi K2.5
|
|
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
|
|
# model: kimi-k2.5
|
|
# api_base: https://api.moonshot.cn/v1
|
|
# api_key: $MOONSHOT_API_KEY
|
|
# timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 32768
|
|
# supports_thinking: true
|
|
# supports_vision: true # Check your specific model's capabilities
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: Novita AI (OpenAI-compatible)
|
|
# Novita provides an OpenAI-compatible API with competitive pricing
|
|
# See: https://novita.ai
|
|
# - name: novita-deepseek-v3.2
|
|
# display_name: Novita DeepSeek V3.2
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: deepseek/deepseek-v3.2
|
|
# api_key: $NOVITA_API_KEY
|
|
# base_url: https://api.novita.ai/openai
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 0.7
|
|
# supports_thinking: true
|
|
# supports_vision: true
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: StepFun (阶跃星辰) reasoning models
|
|
# StepFun provides OpenAI-compatible API with reasoning models.
|
|
# With reasoning_format: deepseek-style, the API returns reasoning_content
|
|
# (same field as DeepSeek), which must be replayed on historical assistant
|
|
# messages in multi-turn tool-call conversations.
|
|
# Use PatchedChatStepFun instead of plain ChatOpenAI.
|
|
# Docs: https://platform.stepfun.com/docs/api-reference/chat-completions
|
|
# - name: step-3.7-flash
|
|
# display_name: Step 3.7 Flash
|
|
# use: deerflow.models.patched_stepfun:PatchedChatStepFun
|
|
# model: step-3.7-flash
|
|
# api_key: $STEPFUN_API_KEY
|
|
# base_url: https://api.stepfun.com/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# supports_thinking: true
|
|
# supports_reasoning_effort: true
|
|
# supports_vision: true
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# reasoning_format: deepseek-style
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# reasoning_format: deepseek-style
|
|
|
|
# Example: MiniMax (OpenAI-compatible) - International Edition
|
|
# MiniMax provides high-performance models with 512K context window and 128K max output
|
|
# Docs: https://platform.minimax.io/docs/api-reference/text-openai-api
|
|
# - name: minimax-m3
|
|
# display_name: MiniMax M3
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M3
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimax.io/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: true
|
|
# supports_thinking: true
|
|
# # PatchedChatMiniMax is the MiniMax adapter: it enables reasoning_split and
|
|
# # maps MiniMax's structured reasoning into reasoning_content (the field
|
|
# # DeerFlow understands), and it strips the per-message `name` field that
|
|
# # DeerFlow middlewares attach — MiniMax rejects requests whose user-message
|
|
# # names differ with "user name must be consistent (2013)". Declare the
|
|
# # thinking toggle so non-thinking paths (flash mode, follow-up suggestions,
|
|
# # title/memory generation) truly disable reasoning instead of spending
|
|
# # tokens on it.
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: adaptive
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# NOTE: M2.x models always think — passing thinking:{type:disabled} has no
|
|
# effect (per MiniMax docs), so the toggle above is omitted for M2.7. The
|
|
# follow-up-suggestions endpoint strips inline <think> defensively regardless.
|
|
# Still use the PatchedChatMiniMax adapter: it strips the per-message `name`
|
|
# field DeerFlow middlewares attach, which MiniMax otherwise rejects with
|
|
# "user name must be consistent (2013)".
|
|
# - name: minimax-m2.7
|
|
# display_name: MiniMax M2.7
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M2.7
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimax.io/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: false # M2.7 is text-only; M3 supports vision
|
|
# supports_thinking: true
|
|
|
|
# - name: minimax-m2.7-highspeed
|
|
# display_name: MiniMax M2.7 Highspeed
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M2.7-highspeed
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimax.io/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: false # M2.7 is text-only; M3 supports vision
|
|
# supports_thinking: true
|
|
|
|
# Example: MiniMax (OpenAI-compatible) - CN 中国区用户
|
|
# MiniMax provides high-performance models with 512K context window and 128K max output
|
|
# Docs: https://platform.minimaxi.com/docs/api-reference/text-openai-api
|
|
# - name: minimax-m3
|
|
# display_name: MiniMax M3
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M3
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimaxi.com/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: true
|
|
# supports_thinking: true
|
|
# # PatchedChatMiniMax is the MiniMax adapter: it enables reasoning_split and
|
|
# # maps MiniMax's structured reasoning into reasoning_content (the field
|
|
# # DeerFlow understands), and it strips the per-message `name` field that
|
|
# # DeerFlow middlewares attach — MiniMax rejects requests whose user-message
|
|
# # names differ with "user name must be consistent (2013)". Declare the
|
|
# # thinking toggle so non-thinking paths (flash mode, follow-up suggestions,
|
|
# # title/memory generation) truly disable reasoning instead of spending
|
|
# # tokens on it.
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: adaptive
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# NOTE: M2.x models always think — passing thinking:{type:disabled} has no
|
|
# effect (per MiniMax docs), so the toggle above is omitted for M2.7. The
|
|
# follow-up-suggestions endpoint strips inline <think> defensively regardless.
|
|
# Still use the PatchedChatMiniMax adapter: it strips the per-message `name`
|
|
# field DeerFlow middlewares attach, which MiniMax otherwise rejects with
|
|
# "user name must be consistent (2013)".
|
|
# - name: minimax-m2.7
|
|
# display_name: MiniMax M2.7
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M2.7
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimaxi.com/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: false # M2.7 is text-only; M3 supports vision
|
|
# supports_thinking: true
|
|
|
|
# - name: minimax-m2.7-highspeed
|
|
# display_name: MiniMax M2.7 Highspeed
|
|
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
|
|
# model: MiniMax-M2.7-highspeed
|
|
# api_key: $MINIMAX_API_KEY
|
|
# base_url: https://api.minimaxi.com/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 4096
|
|
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
|
|
# supports_vision: false # M2.7 is text-only; M3 supports vision
|
|
# supports_thinking: true
|
|
|
|
# Example: OpenRouter (OpenAI-compatible)
|
|
# OpenRouter models use the same ChatOpenAI + base_url pattern as other OpenAI-compatible gateways.
|
|
# - name: openrouter-gemini-2.5-flash
|
|
# display_name: Gemini 2.5 Flash (OpenRouter)
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: google/gemini-2.5-flash-preview
|
|
# api_key: $OPENAI_API_KEY
|
|
# base_url: https://openrouter.ai/api/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# temperature: 0.7
|
|
|
|
# Example: Atlas Cloud (OpenAI-compatible)
|
|
# Atlas Cloud exposes a single OpenAI-compatible endpoint in front of many open
|
|
# models (DeepSeek, Qwen, Kimi, GLM, MiniMax, Llama, ...), so it uses the same
|
|
# ChatOpenAI + base_url pattern as other OpenAI-compatible gateways.
|
|
# Browse model ids at https://api.atlascloud.ai/v1/models — see https://atlascloud.ai
|
|
# - name: atlascloud-deepseek-v3.2
|
|
# display_name: DeepSeek V3.2 (Atlas Cloud)
|
|
# use: langchain_openai:ChatOpenAI
|
|
# model: deepseek-ai/DeepSeek-V3.2-Exp
|
|
# api_key: $ATLASCLOUD_API_KEY
|
|
# base_url: https://api.atlascloud.ai/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# temperature: 0.7
|
|
# supports_vision: false
|
|
#
|
|
# For reasoning models on Atlas Cloud (e.g. a Qwen3 *-thinking id), use the
|
|
# patched OpenAI-compatible adapter so reasoning_content is replayed across
|
|
# multi-turn tool-call conversations:
|
|
# - name: atlascloud-qwen3-thinking
|
|
# display_name: Qwen3 235B Thinking (Atlas Cloud)
|
|
# use: deerflow.models.patched_openai:PatchedChatOpenAI
|
|
# model: qwen/qwen3-235b-a22b-thinking-2507
|
|
# api_key: $ATLASCLOUD_API_KEY
|
|
# base_url: https://api.atlascloud.ai/v1
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: enabled
|
|
# when_thinking_disabled:
|
|
# extra_body:
|
|
# thinking:
|
|
# type: disabled
|
|
|
|
# Example: vLLM 0.19.0 (OpenAI-compatible, with reasoning toggle)
|
|
# DeerFlow's vLLM provider preserves vLLM reasoning across tool-call turns and
|
|
# toggles Qwen-style reasoning by writing
|
|
# extra_body.chat_template_kwargs.enable_thinking=true/false.
|
|
# Some reasoning models also require the server to be started with
|
|
# `vllm serve ... --reasoning-parser <parser>`.
|
|
# - name: qwen3-32b-vllm
|
|
# display_name: Qwen3 32B (vLLM)
|
|
# use: deerflow.models.vllm_provider:VllmChatModel
|
|
# model: Qwen/Qwen3-32B
|
|
# api_key: $VLLM_API_KEY
|
|
# base_url: http://localhost:8000/v1
|
|
# # Enable only when the endpoint reports cumulative usage on every stream chunk.
|
|
# cumulative_stream_usage: true
|
|
# request_timeout: 600.0
|
|
# max_retries: 2
|
|
# max_tokens: 8192
|
|
# supports_thinking: true
|
|
# supports_vision: false
|
|
# when_thinking_enabled:
|
|
# extra_body:
|
|
# chat_template_kwargs:
|
|
# enable_thinking: true
|
|
|
|
|
|
# Example: Qwen3-Coder deployed on MindIE Engine
|
|
# - name: Qwen3_Coder_480B_MindIE
|
|
# display_name: Qwen3-Coder-480B (MindIE)
|
|
# use: deerflow.models.mindie_provider:MindIEChatModel
|
|
# model: Qwen3-Coder-480B-A35B-Instruct-Client
|
|
# base_url: http://localhost:8989/v1
|
|
# api_key: $OPENAI_API_KEY
|
|
# temperature: 0
|
|
# max_retries: 1
|
|
# supports_thinking: false
|
|
# supports_vision: false
|
|
# supports_reasoning_effort: false
|
|
# # --- Advanced Network Settings ---
|
|
# # Due to MindIE's streaming limitations with tool calling, the provider
|
|
# # uses mock-streaming (awaiting full generation). Extended timeouts are required.
|
|
# read_timeout: 900.0 # 15 minutes to prevent drops during long document generation
|
|
# connect_timeout: 30.0
|
|
# write_timeout: 60.0
|
|
# pool_timeout: 30.0
|
|
|
|
# ============================================================================
|
|
# Knowledge Capability
|
|
# ============================================================================
|
|
# Optional knowledge capability. The feature is disabled by default. Provider
|
|
# connection and retrieval settings belong to the matching entry in `tools:`
|
|
# below (for example, the RAGFlow `knowledge_search` tool), not here.
|
|
knowledge_base:
|
|
enabled: false
|
|
# Custom-agent chat only. Keeps the selector hidden unless the effective
|
|
# knowledge_search provider is the bundled RAGFlow tool.
|
|
scope_selection_enabled: false
|
|
|
|
# ============================================================================
|
|
# Tool Groups Configuration
|
|
# ============================================================================
|
|
# Define groups of tools for organization and access control
|
|
|
|
tool_groups:
|
|
- name: web
|
|
- name: file:read
|
|
- name: file:write
|
|
- name: bash
|
|
- name: browser
|
|
- name: knowledge
|
|
|
|
# ============================================================================
|
|
# Tools Configuration
|
|
# ============================================================================
|
|
# Configure available tools for the agent to use
|
|
|
|
tools:
|
|
# Read explicitly referenced conversations (Gateway API only, opt-in).
|
|
# Each run must submit conversation_references; see backend/docs/API.md.
|
|
# - name: read_conversation
|
|
# group: conversation
|
|
# use: deerflow.tools.conversation:read_conversation
|
|
|
|
# RAGFlow knowledge retrieval (read-only). Uncomment this single entry.
|
|
# `datasets` is optional. Omit it to list every tenant-visible dataset at
|
|
# search time; an explicit `datasets: []` is invalid. Empty datasets are
|
|
# skipped; searchable datasets are grouped by
|
|
# embedding model and retrieved with at most four groups in parallel. Group
|
|
# ranks are interleaved under the global `page_size` limit; scores are omitted
|
|
# when multiple groups are searched because they are not comparable.
|
|
# Configure stable IDs only to restrict scope. IDs and catalog listing never
|
|
# reach the model.
|
|
# - name: knowledge_search
|
|
# group: knowledge
|
|
# use: deerflow.community.ragflow.tools:knowledge_search_tool
|
|
# base_url: http://localhost:9380 # Docker: use a backend-reachable URL
|
|
# api_key: $RAGFLOW_API_KEY
|
|
# datasets: # Optional operator-controlled allowlist
|
|
# - 0123456789abcdef0123456789abcdef # Replace with a RAGFlow dataset ID
|
|
# 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
|
|
|
|
# LightRAG knowledge retrieval (read-only). Alternative provider for the same
|
|
# knowledge_search tool; uncomment this entry instead of the RAGFlow one —
|
|
# duplicate names keep the first entry, so configure exactly one. Requires
|
|
# LightRAG v1.4.9+ (response envelope and citation fields). Searches the deployment's
|
|
# single indexed workspace through the data retrieval endpoint (no LLM
|
|
# generation inside LightRAG). `api_key` is optional; only omit it for
|
|
# loopback/trusted-network servers — when auth is enabled the key is sent as
|
|
# the X-API-Key header. `mode` picks the retrieval strategy: naive, local,
|
|
# global, hybrid, or mix (default mix, matching LightRAG's own default).
|
|
# Internal chunk and reference identifiers never reach the model.
|
|
# - name: knowledge_search
|
|
# group: knowledge
|
|
# use: deerflow.community.lightrag.tools:knowledge_search_tool
|
|
# base_url: http://localhost:9621 # Docker: use a backend-reachable URL
|
|
# api_key: $LIGHTRAG_API_KEY # Omit only for unauthenticated trusted servers
|
|
# 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
|
|
|
|
# Web search tool (uses DuckDuckGo, no API key required)
|
|
- name: web_search
|
|
group: web
|
|
use: deerflow.community.ddg_search.tools:web_search_tool
|
|
max_results: 5
|
|
# backend: auto # DDGS backend(s): auto, duckduckgo, brave, wikipedia, etc.
|
|
# region: wt-wt # wt-wt is normalized for Wikipedia when backend includes auto/all/wikipedia.
|
|
# safesearch: moderate # on, moderate, off
|
|
|
|
# Web search tool (uses SearXNG, self-hosted, no API key required)
|
|
# SearXNG is a free internet metasearch engine which aggregates results from
|
|
# various search services. Deploy your own instance: https://github.com/searxng/searxng
|
|
# For Docker deployments, use the Docker service name instead of localhost.
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.searxng.tools:web_search_tool
|
|
# base_url: http://localhost:8088 # SearXNG instance URL (default: :8088; Docker: http://searxng:8080)
|
|
# max_results: 5 # Maximum number of search results
|
|
|
|
# Web search tool (uses Serper - Google Search API, requires SERPER_API_KEY)
|
|
# Serper provides real-time Google Search results. Sign up at https://serper.dev
|
|
# Note: set SERPER_API_KEY in your environment before starting the app.
|
|
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.serper.tools:web_search_tool
|
|
# max_results: 5 # capped at 10 by the Serper provider
|
|
# include_domains: [] # Optional domain-only lists, at most 10 entries each
|
|
# exclude_domains: [] # Exclusion wins; see backend/docs/CONFIGURATION.md
|
|
# # api_key: $SERPER_API_KEY # Optional explicit env-var reference
|
|
|
|
# Web search tool (uses Serply - Google Search API, requires SERPLY_API_KEY)
|
|
# Serply returns live Google results and covers Google News and Google Scholar
|
|
# with the same key. Sign up at https://serply.io (docs: https://serply.io/docs)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.serply.tools:web_search_tool
|
|
# max_results: 5 # Serply accepts 1-100 per request
|
|
# # vertical: search # search (default), news, or scholar
|
|
# # gl: us # Optional country code for the results
|
|
# # hl: en # Optional interface language
|
|
# # api_key: $SERPLY_API_KEY # Optional if the env var is set
|
|
|
|
# Web search tool (uses Brave Search API, requires BRAVE_SEARCH_API_KEY)
|
|
# Brave Search returns results from an independent index. Sign up at
|
|
# https://brave.com/search/api/ to get a key. Unlike the DuckDuckGo
|
|
# `backend: brave` option above, this calls the official Brave API directly.
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.brave.tools:web_search_tool
|
|
# max_results: 5 # Capped at 20 by the Brave Search API
|
|
# # api_key: $BRAVE_SEARCH_API_KEY # Optional if the env var is set
|
|
|
|
# Web search tool (requires Tavily API key)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.tavily.tools:web_search_tool
|
|
# max_results: 5
|
|
# # Optional search-source filters; omit or use [] for no restriction of that kind.
|
|
# # include_domains:
|
|
# # - docs.python.org
|
|
# # - developer.mozilla.org
|
|
# # exclude_domains: []
|
|
# # api_key: $TAVILY_API_KEY # Set if needed
|
|
|
|
# Web search tool (uses InfoQuest, requires InfoQuest API key)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.infoquest.tools:web_search_tool
|
|
# # Used to limit the scope of search results, only returns content within the specified time range. Set to -1 to disable time filtering
|
|
# search_time_range: 10
|
|
|
|
# Web search tool (uses Tencent Cloud Web Search API, requires a service API key)
|
|
# Create a service API key in the Tencent Cloud WSA console, then set
|
|
# TENCENTCLOUD_WSA_APIKEY in the Gateway environment. Do not use Tencent Cloud
|
|
# SecretId/SecretKey for this provider.
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.tencent_wsa.tools:web_search_tool
|
|
# max_results: 5 # 1-50; values above 10 request Tencent's Cnt option
|
|
# # (requires a Tencent plan that supports Cnt)
|
|
# # mode: 0 # Optional: 0=web, 1=VR, 2=mixed
|
|
# # api_key: $TENCENTCLOUD_WSA_APIKEY # Optional explicit env-var reference
|
|
|
|
# Web search tool (uses Exa, requires EXA_API_KEY)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.exa.tools:web_search_tool
|
|
# max_results: 5
|
|
# search_type: auto # Options: auto, neural, keyword
|
|
# contents_max_characters: 1000
|
|
# # api_key: $EXA_API_KEY
|
|
|
|
# Web search tool (uses Firecrawl. Cloud requires FIRECRAWL_API_KEY; self-host may need no key.)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.firecrawl.tools:web_search_tool
|
|
# max_results: 5
|
|
# # api_key: $FIRECRAWL_API_KEY
|
|
# # base_url: http://<host>:3002 # optional: self-hosted Firecrawl; no cloud API key needed
|
|
|
|
# Web search tool (uses GroundRoute, requires GROUNDROUTE_API_KEY)
|
|
# GroundRoute is a meta search layer: one API in front of six engines (Serper,
|
|
# Brave, Exa, Tavily, Firecrawl, Perplexity). It routes each query to the cheapest
|
|
# engine that clears a quality bar and fails over if one is down. Pricing is
|
|
# gain-share (you keep about half of any cache savings). Get a key at
|
|
# https://groundroute.ai/keys
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.groundroute.tools:web_search_tool
|
|
# max_results: 5 # Clamped to 1-50 by GroundRoute
|
|
# # api_key: $GROUNDROUTE_API_KEY # Optional if the env var is set
|
|
|
|
# Web search tool (uses fastCRW - Firecrawl-compatible web scraper, single binary,
|
|
# self-host or cloud. Cloud requires CRW_API_KEY; self-host may need no key.)
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.fastcrw.tools:web_search_tool
|
|
# max_results: 5
|
|
# # api_key: $CRW_API_KEY
|
|
# # base_url: https://fastcrw.com/api # default cloud; set to e.g. http://localhost:3000 for self-host
|
|
|
|
# Web search tool (uses Sofya, requires SOFYA_API_KEY)
|
|
# Sofya returns the content of the result pages, not only their snippets.
|
|
# Sign up at https://sofya.co to get a key.
|
|
# Note: set SOFYA_API_KEY in your environment before starting the app.
|
|
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
|
|
# - name: web_search
|
|
# group: web
|
|
# use: deerflow.community.sofya.tools:web_search_tool
|
|
# max_results: 5 # capped at 20 by the Sofya provider
|
|
# # search_depth: basic # basic reads the result pages, snippets returns snippets only
|
|
# contents_max_characters: 2000 # per-result content cap; 0 disables it
|
|
# # api_key: $SOFYA_API_KEY # Optional explicit env-var reference
|
|
|
|
# Web fetch tool (uses Browserless - headless Chrome, self-hosted or cloud)
|
|
# Browserless renders pages with a real headless Chrome, ideal for JavaScript-heavy
|
|
# sites and SPAs. Deploy your own: https://github.com/browserless/browserless
|
|
# For Docker deployments, use the Docker service name instead of localhost.
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry below before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.browserless.tools:web_fetch_tool
|
|
# base_url: http://localhost:3032 # Browserless instance URL (default: :3032; Docker: http://browserless:3000)
|
|
# # token: $BROWSERLESS_TOKEN # API token (required for Browserless Cloud; optional for self-hosted)
|
|
# timeout_s: 30 # Request timeout in seconds
|
|
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
|
|
# # wait_for_event: "networkidle" # Wait for a page event before returning (e.g. "load", "networkidle")
|
|
# # wait_for_timeout_ms: 2000 # Extra wait after page load in milliseconds
|
|
# # wait_for_selector: "article" # CSS selector to wait for before returning
|
|
# # wait_for_selector_timeout_ms: 5000 # How long to wait for that selector, in milliseconds
|
|
# # reject_resource_types: ["image", "media", "font"] # Skip these resource types
|
|
# # reject_request_pattern: ["\\.css$", "\\.png$"] # Skip URLs matching these regexes
|
|
# # # Both accept a YAML list or a
|
|
# # # comma-separated string. Regexes containing
|
|
# # # commas must use the YAML list form.
|
|
|
|
# Web fetch tool (uses Crawl4AI - self-hosted headless Chromium, no third-party API key)
|
|
# Crawl4AI returns server-cleaned "fit" markdown directly (no readability step needed),
|
|
# ideal for JavaScript-heavy sites. Self-host it:
|
|
# docker run -d -p 11235:11235 --shm-size=1g \
|
|
# -e CRAWL4AI_API_TOKEN=$CRAWL4AI_TOKEN unclecode/crawl4ai:0.9.2
|
|
# Crawl4AI >= 0.9 is secure-by-default: a bearer token is REQUIRED on every request
|
|
# except GET /health, and a server started without CRAWL4AI_API_TOKEN binds 127.0.0.1
|
|
# only (so a container/remote DeerFlow cannot reach it at all). Set the same value in
|
|
# the server env and in `token:` below, or requests fail with HTTP 401.
|
|
# Use >= 0.8.7: earlier images, including 0.8.6, carry known pre-auth RCEs.
|
|
# For Docker deployments, use the Docker service name instead of localhost.
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry below before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.crawl4ai.tools:web_fetch_tool
|
|
# base_url: http://localhost:11235 # Crawl4AI server URL (Docker: http://crawl4ai:11235)
|
|
# token: $CRAWL4AI_TOKEN # Bearer token; required by Crawl4AI >= 0.9
|
|
# timeout: 30 # Request timeout in seconds
|
|
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
|
|
# # filter: fit # Markdown filter: fit (default) | raw | bm25 | llm
|
|
|
|
# Web capture tool (uses Browserless /screenshot to render a page as an artifact)
|
|
# Browserless captures JavaScript-heavy pages with a real headless Chrome and
|
|
# writes the screenshot into the current thread's outputs. It can run against
|
|
# a self-hosted Browserless instance without a token, or Browserless Cloud with
|
|
# BROWSERLESS_TOKEN. For Docker deployments, use the Docker service name instead
|
|
# of localhost.
|
|
# - name: web_capture
|
|
# group: web
|
|
# use: deerflow.community.browserless.tools:web_capture_tool
|
|
# base_url: http://localhost:3032 # Browserless instance URL (Docker: http://browserless:3000)
|
|
# # token: $BROWSERLESS_TOKEN # Required for Browserless Cloud; optional for self-hosted
|
|
# timeout_s: 30 # Request timeout in seconds (`timeout` also accepted)
|
|
# output_format: png # png, jpeg, or webp
|
|
# full_page: true # Capture entire page instead of viewport only
|
|
# viewport_width: 1280
|
|
# viewport_height: 720
|
|
# # wait_for_selector: "main" # CSS selector to wait for before capturing
|
|
# # wait_for_selector_timeout_ms: 5000
|
|
# # wait_for_timeout_ms: 1000 # Extra wait after navigation in milliseconds
|
|
# # best_attempt: true # Continue with current page state if waits time out
|
|
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY to
|
|
# # # capture internal/private targets (loopback, RFC1918, etc.)
|
|
|
|
# Agentic browser control (stateful navigate → observe → click/type loop).
|
|
# Unlike web_fetch/web_capture (read-only, stateless), these tools keep a live
|
|
# per-thread Playwright browser so the agent can interact with JavaScript-heavy
|
|
# pages, fill forms, and follow multi-step flows. Elements are addressed by the
|
|
# numeric [ref] index returned in each page snapshot.
|
|
#
|
|
# Requires the optional dependency, installed once per environment:
|
|
# cd backend && uv sync --extra browser && uv run playwright install chromium
|
|
# make dev / Docker startup auto-detect an active browser_navigate entry and
|
|
# preserve the browser extra during dependency sync.
|
|
#
|
|
# Uncomment the whole block to enable. Launch and URL-policy settings are read
|
|
# canonically from browser_navigate and shared by every browser tool + Live.
|
|
# - name: browser_navigate
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_navigate_tool
|
|
# headless: true # Set false only for local, trusted debugging
|
|
# timeout_ms: 30000 # Per-action navigation/interaction timeout
|
|
# viewport_width: 1280
|
|
# viewport_height: 720
|
|
# # allow_private_addresses: false # SSRF guard: keep false in production. Also applies to the
|
|
# # # pinned egress proxy every launched browser connects through.
|
|
# # cdp_url: http://127.0.0.1:9222 # Attach to YOUR running Chrome (Codex-style)
|
|
# # allow_unguarded_cdp: false # REQUIRED true with cdp_url: attaching to an
|
|
# # # existing Chrome prevents DeerFlow from
|
|
# # # enforcing its subresource/redirect SSRF guard.
|
|
# # # instead of launching a private headless one.
|
|
# # # Start Chrome with --remote-debugging-port=9222
|
|
# # # so you watch the agent drive your real browser
|
|
# # # with your real login sessions. Local/trusted only;
|
|
# # # never enable for an untrusted CDP endpoint.
|
|
# # # Chrome must be version-matched to the bundled
|
|
# # # Playwright; a much newer Chrome can reject the
|
|
# # # CDP handshake. If so, use headless: false to launch
|
|
# # # Playwright's own visible browser instead.
|
|
# # Multi-worker note: browser sessions live in one Gateway worker's memory.
|
|
# # Keep the Gateway to one worker process while this tool group is enabled:
|
|
# # GATEWAY_WORKERS=1, and WEB_CONCURRENCY unset or 1 on the launches that pass
|
|
# # uvicorn no --workers (backend/Dockerfile, scripts/serve.sh).
|
|
# - name: browser_snapshot
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_snapshot_tool
|
|
# - name: browser_click
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_click_tool
|
|
# - name: browser_type
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_type_tool
|
|
# - name: browser_get_text
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_get_text_tool
|
|
# max_chars: 8000 # Truncation cap for browser_get_text output
|
|
# - name: browser_back
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_back_tool
|
|
# - name: browser_screenshot
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_screenshot_tool
|
|
# - name: browser_close
|
|
# group: browser
|
|
# use: deerflow.community.browser_automation.tools:browser_close_tool
|
|
|
|
# Web fetch tool (uses Exa)
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry below before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.exa.tools:web_fetch_tool
|
|
# # api_key: $EXA_API_KEY
|
|
|
|
# Web fetch tool (uses Jina AI reader)
|
|
- name: web_fetch
|
|
group: web
|
|
use: deerflow.community.jina_ai.tools:web_fetch_tool
|
|
timeout: 10
|
|
# Opt-in retries may increase upstream requests and cost.
|
|
# max_retries: 0
|
|
# retry_budget_seconds: 30
|
|
# Optional proxy for restricted networks / Docker / WSL.
|
|
# Use host.docker.internal instead of 127.0.0.1 when the proxy runs on the host.
|
|
# proxy: $HTTPS_PROXY
|
|
# trust_env: true
|
|
|
|
# Web fetch tool (uses InfoQuest)
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.infoquest.tools:web_fetch_tool
|
|
# # Overall timeout for the entire crawling process (in seconds). Set to positive value to enable, -1 to disable
|
|
# timeout: 10
|
|
# # Waiting time after page loading (in seconds). Set to positive value to enable, -1 to disable
|
|
# fetch_time: 10
|
|
# # Timeout for navigating to the page (in seconds). Set to positive value to enable, -1 to disable
|
|
# navigation_timeout: 30
|
|
|
|
# Web fetch tool (uses Firecrawl. Cloud requires FIRECRAWL_API_KEY; self-host may need no key.)
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.firecrawl.tools:web_fetch_tool
|
|
# # api_key: $FIRECRAWL_API_KEY
|
|
# # base_url: http://<host>:3002 # optional: self-hosted Firecrawl; no cloud API key needed
|
|
|
|
# Web fetch tool (uses GroundRoute, requires GROUNDROUTE_API_KEY)
|
|
# Fetches a page's extracted text via GroundRoute mode=page.
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry above before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.groundroute.tools:web_fetch_tool
|
|
# # api_key: $GROUNDROUTE_API_KEY
|
|
|
|
# Web fetch tool (uses fastCRW - Firecrawl-compatible web scraper, single binary,
|
|
# self-host or cloud. Cloud requires CRW_API_KEY; self-host may need no key.)
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry above before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.fastcrw.tools:web_fetch_tool
|
|
# # api_key: $CRW_API_KEY
|
|
# # base_url: https://fastcrw.com/api # default cloud; set to e.g. http://localhost:3000 for self-host
|
|
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
|
|
|
|
# Web fetch tool (uses Sofya, requires SOFYA_API_KEY)
|
|
# Returns the page as markdown. Also handles PDF, DOCX and other documents.
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry above before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.sofya.tools:web_fetch_tool
|
|
# # api_key: $SOFYA_API_KEY
|
|
|
|
# Web fetch tool (uses Unbrowse, requires UNBROWSE_API_KEY)
|
|
# Returns the page as markdown. Fetches over plain HTTP when that is enough and
|
|
# falls back to a hosted cloud browser for pages that need JavaScript.
|
|
# Get a key at https://unbrowse.ai/app.
|
|
# NOTE: Only one web_fetch provider can be active at a time.
|
|
# Comment out the Jina AI web_fetch entry above before enabling this one.
|
|
# - name: web_fetch
|
|
# group: web
|
|
# use: deerflow.community.unbrowse.tools:web_fetch_tool
|
|
# # render: auto # auto | never (plain HTTP only) | always (cloud browser)
|
|
# # api_key: $UNBROWSE_API_KEY
|
|
|
|
# Image search tool (uses DuckDuckGo)
|
|
# Use this to find reference images before image generation
|
|
- name: image_search
|
|
group: web
|
|
use: deerflow.community.image_search.tools:image_search_tool
|
|
max_results: 5
|
|
|
|
# Image search tool (uses InfoQuest)
|
|
# - name: image_search
|
|
# group: web
|
|
# use: deerflow.community.infoquest.tools:image_search_tool
|
|
# # Used to limit the scope of image search results, only returns content within the specified time range. Set to -1 to disable time filtering
|
|
# image_search_time_range: 10
|
|
# # Image size filter. Options: "l" (large), "m" (medium), "i" (icon).
|
|
# image_size: "i"
|
|
|
|
# Image search tool (uses Serper - Google Images API, requires SERPER_API_KEY)
|
|
# Serper provides real-time Google Images results. Sign up at https://serper.dev
|
|
# Note: set SERPER_API_KEY in your environment before starting the app.
|
|
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
|
|
# - name: image_search
|
|
# group: web
|
|
# use: deerflow.community.serper.tools:image_search_tool
|
|
# max_results: 5 # capped at 10 by the Serper provider
|
|
# # api_key: $SERPER_API_KEY # Optional explicit env-var reference
|
|
|
|
# Image search tool (uses Brave Image Search API, requires BRAVE_SEARCH_API_KEY)
|
|
# Brave provides independent image results and works alongside the Brave web search tool.
|
|
# Note: set BRAVE_SEARCH_API_KEY in your environment before starting the app.
|
|
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
|
|
# - name: image_search
|
|
# group: web
|
|
# use: deerflow.community.brave.tools:image_search_tool
|
|
# max_results: 5 # capped at 200 by Brave Image Search
|
|
# # country: US
|
|
# # search_lang: en
|
|
# # safesearch: strict
|
|
# # spellcheck: true
|
|
# # api_key: $BRAVE_SEARCH_API_KEY # Optional explicit env-var reference
|
|
|
|
# File operations tools
|
|
- 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
|
|
|
|
# Bash execution tool
|
|
# Active only when using an isolated shell sandbox or when
|
|
# sandbox.allow_host_bash: true explicitly opts into host bash.
|
|
- name: bash
|
|
group: bash
|
|
use: deerflow.sandbox.tools:bash_tool
|
|
|
|
# ============================================================================
|
|
# Tool Search Configuration (Deferred Tool Loading)
|
|
# ============================================================================
|
|
# When enabled, MCP tools are not loaded into the agent's context directly.
|
|
# Instead, they are listed by name in the system prompt and discoverable
|
|
# via the tool_search tool at runtime.
|
|
# This reduces context usage and improves tool selection accuracy when
|
|
# multiple MCP servers expose a large number of tools.
|
|
|
|
tool_search:
|
|
enabled: false
|
|
# When tool_search is enabled, PR1 MCP routing metadata can auto-promote
|
|
# matching deferred MCP tool schemas before a model call. This is the maximum
|
|
# number of matched schemas promoted per model call. Valid range: 1..5.
|
|
auto_promote_top_k: 3
|
|
|
|
# ============================================================================
|
|
# Tool Output Budget Protection
|
|
# ============================================================================
|
|
# Prevents oversized tool results from blowing the model context window.
|
|
# Outputs exceeding `externalize_min_chars` are persisted to disk and replaced
|
|
# with a compact typed synopsis + file reference. The model can read the full output
|
|
# via read_file. When disk persistence is unavailable, outputs exceeding
|
|
# `fallback_max_chars` are head+tail truncated instead.
|
|
#
|
|
# `exempt_tools` prevents persist→read→persist infinite loops for read tools.
|
|
# `tool_overrides` allows per-tool threshold customization.
|
|
#
|
|
# The same middleware also elides the `content` argument of successful
|
|
# write_file calls from model-bound requests once the same path was read or
|
|
# modified again later (issue #5328): the file on disk is then the source of
|
|
# truth, and the read-before-write gate forces a read_file before the next
|
|
# modification anyway, so the historical copy only costs context. Stored
|
|
# history, receipts, and the run journal keep the original arguments.
|
|
|
|
tool_output:
|
|
enabled: true
|
|
externalize_min_chars: 12000
|
|
# Sampling budget for the inline raw head/tail sample appended to every
|
|
# typed synopsis; ignored for binary-like output, which carries its own sample.
|
|
preview_head_chars: 2000
|
|
preview_tail_chars: 1000
|
|
fallback_max_chars: 30000
|
|
fallback_head_chars: 8000
|
|
fallback_tail_chars: 3000
|
|
storage_subdir: ".tool-results"
|
|
exempt_tools:
|
|
- read_file
|
|
- read_file_tool
|
|
# tool_overrides:
|
|
# web_search: 8000
|
|
# bash: 20000
|
|
# Replace a successful write_file call's `content` with a short placeholder
|
|
# pointing at read_file once a later successful read_file / write_file /
|
|
# str_replace of the same path exists. Only the model-bound request changes.
|
|
elide_superseded_writes: true
|
|
# Only elide content at least this many characters long (a character count,
|
|
# not tokens: CJK text costs ~3-4x more per character than ASCII). 0 elides
|
|
# every non-empty content.
|
|
superseded_write_min_chars: 2000
|
|
# The newest N successful write_file calls (across all paths) always stay
|
|
# visible, even when superseded, so the model can still say what it just
|
|
# wrote without a read. 0 keeps none.
|
|
keep_recent_writes: 1
|
|
|
|
# ============================================================================
|
|
# Tool Artifact Handle Registry Configuration (issue #4676)
|
|
# ============================================================================
|
|
# Captures lightweight artifact references (paths, URLs, task ids) from tool
|
|
# results into durable per-thread state (`ThreadState.tool_artifacts`) so they
|
|
# survive context compaction. The model references artifacts by short handles
|
|
# (`art_xxxxxxxx`) that are resolved to real references before tool execution.
|
|
# Handles are listed in the model context and rendered as badges in the UI.
|
|
|
|
tool_artifacts:
|
|
enabled: true
|
|
# Maximum artifact entries retained per thread (a sliding window: the
|
|
# oldest entries are evicted when the cap is exceeded).
|
|
max_entries: 100
|
|
# Conservatively scan free-text tool results for sandbox paths / remote file URLs.
|
|
detect_refs_in_text: true
|
|
# Project available artifact handles into the model request as durable context.
|
|
inject_model_context: true
|
|
# Resolve artifact handles found in tool arguments to real references before execution.
|
|
resolve_handles_in_args: true
|
|
|
|
# ============================================================================
|
|
# Suggestions Configuration
|
|
# ============================================================================
|
|
# Configure whether the agent automatically generates follow-up question
|
|
# suggestions at the end of each response.
|
|
|
|
suggestions:
|
|
enabled: true
|
|
max_suggestions: 3
|
|
|
|
|
|
# ============================================================================
|
|
# Input Polish Configuration
|
|
# ============================================================================
|
|
# Configure whether the composer can rewrite draft input before sending.
|
|
|
|
input_polish:
|
|
enabled: true
|
|
# Maximum draft length accepted by /api/input-polish.
|
|
max_chars: 4000
|
|
# Optional fast model for draft polishing. Leave null to use the default chat model.
|
|
# For best UX, set this to your lowest-latency inexpensive model.
|
|
model_name: null
|
|
|
|
|
|
# ============================================================================
|
|
# Loop Detection Configuration
|
|
# ============================================================================
|
|
# Detect and interrupt repeated identical tool-call loops.
|
|
# Frequency thresholds are safety limits for repeated use of the same tool type.
|
|
|
|
loop_detection:
|
|
enabled: true
|
|
warn_threshold: 3
|
|
hard_limit: 5
|
|
window_size: 20
|
|
# Compatibility name: bounds retained (thread_id, run_id) histories, not whole threads.
|
|
max_tracked_threads: 100
|
|
tool_freq_warn: 30
|
|
tool_freq_hard_limit: 50
|
|
# Per-tool overrides for tool_freq_warn / tool_freq_hard_limit. Values can be
|
|
# higher or lower than the global defaults. Commonly used to raise thresholds
|
|
# for high-frequency tools like bash in batch workflows (e.g. RNA-seq pipelines)
|
|
# without weakening protection on every other tool.
|
|
# tool_freq_overrides:
|
|
# bash:
|
|
# warn: 150
|
|
# hard_limit: 300
|
|
|
|
# ============================================================================
|
|
# PII Redaction Middleware Configuration (issue #3190)
|
|
# ============================================================================
|
|
# Deterministic PII redaction in model-bound context. When enabled, emails,
|
|
# phone numbers, API keys, credit-card numbers (Luhn-validated) and national
|
|
# IDs found in genuine user messages and remote-content tool results
|
|
# (web_fetch / web_search / image_search / web_capture and MCP tools) are
|
|
# rewritten to keyed, value-derived placeholders like
|
|
# [EMAIL_bdqkxwcfmqvrqzblcchjkqbcdqw] before they reach the model. Tokens are
|
|
# HMAC digests keyed by token_secret, so they cannot be re-derived offline.
|
|
# Raw text stays in thread state for the UI; queued memory-extraction
|
|
# payloads are redacted too. Compaction and configured LLM title inputs are
|
|
# redacted as well; placeholders are value-derived, so an identity redacted
|
|
# before compaction renders the same token afterwards.
|
|
pii_redaction:
|
|
enabled: false
|
|
# redact_email: true
|
|
# redact_api_key: true
|
|
# redact_credit_card: true
|
|
# redact_phone: true
|
|
# redact_national_id: true
|
|
# Required when enabled: true (non-empty, at least 16 characters) — the
|
|
# deployment-scoped HMAC secret for the placeholder digests. Tokens are
|
|
# linkable only within this deployment and cannot be re-derived offline
|
|
# without it. Enabling redaction without a valid secret fails config
|
|
# validation: an unkeyed digest would be a publicly computable, globally
|
|
# linkable fingerprint of the raw values (guessable for phone numbers).
|
|
# token_secret: change-me-to-a-long-random-string
|
|
|
|
# ============================================================================
|
|
# Tool Progress State Machine Configuration (RFC #3177)
|
|
# ============================================================================
|
|
# Detects tool stagnation and repetition at the (thread, tool) level.
|
|
# Tracks consecutive "no-new-info" calls (error, partial_success, near-duplicate success).
|
|
# Three transition paths (determined by deerflow_tool_meta.recoverable_by_model):
|
|
# recoverable=true (no_results, not_found, permission): ACTIVE → WARNED (terminal; hint re-injected each call)
|
|
# recoverable=false (rate_limited, transient): ACTIVE → WARNED → BLOCKED after warn_escalation_count more
|
|
# recoverable=false + action=stop (auth, config): ACTIVE → BLOCKED immediately
|
|
# Requires ToolErrorHandlingMiddleware to be active (always on).
|
|
|
|
# tool_progress:
|
|
# enabled: false
|
|
# stagnation_threshold: 3 # Consecutive problems before WARNED
|
|
# warn_escalation_count: 2 # More problems after WARNED before BLOCKED
|
|
# inject_assessment: true
|
|
# jaccard_similarity_threshold: 0.8 # Word-set similarity threshold for near-duplicate detection
|
|
# min_word_count_for_similarity: 10 # Min unique words to apply Jaccard check
|
|
# max_tracked_threads: 100
|
|
# exempt_tools:
|
|
# - ask_clarification
|
|
# - write_todos
|
|
# - present_files
|
|
# - task
|
|
|
|
# ============================================================================
|
|
# Read-Before-Write File Gate (issue #3857)
|
|
# ============================================================================
|
|
# Blocks write_file (append / overwrite of an existing file) and str_replace
|
|
# unless the agent has read the file's current version first; any write
|
|
# invalidates earlier reads, forcing a re-read between consecutive edits.
|
|
# Deterministic guardrail against blind duplicate appends in long tasks.
|
|
|
|
read_before_write:
|
|
enabled: true
|
|
# A blocked call never ran and must be re-issued after a re-read, so its
|
|
# payload (write_file content, str_replace old_str/new_str) is dead weight in
|
|
# every later model call. Replace it with a short placeholder in model-bound
|
|
# requests; stored history, receipts, and the run journal keep the original.
|
|
elide_blocked_payloads: true
|
|
# Only elide payload fields at least this many characters long (a character
|
|
# count, not tokens: CJK text costs ~3-4x more per character than ASCII).
|
|
# 0 elides every non-empty payload.
|
|
elide_min_chars: 2000
|
|
|
|
# ============================================================================
|
|
# Projects
|
|
# ============================================================================
|
|
# User projects group threads under a name plus free-form instructions. Member
|
|
# threads receive the project's current instructions as a bounded, request-
|
|
# scoped <project> block rendered from the snapshot pinned at run admission;
|
|
# the block never enters the system prompt or persisted history. The shelf
|
|
# index bounds below cap the request-scoped <documents> listing rendered from
|
|
# the same pinned snapshot. Oversized instructions are rejected at write time
|
|
# (422) and never truncated.
|
|
|
|
projects:
|
|
# Hard UTF-8 byte cap for project instructions (range: 256-262144). Multi-
|
|
# byte characters count as their UTF-8 byte length, not as characters.
|
|
instructions_max_bytes: 8192
|
|
# Maximum shelf entries rendered into the <documents> index per run
|
|
# (range: 1-500).
|
|
shelf_index_max_entries: 50
|
|
# UTF-8 byte cap for the rendered <documents> index, counting IDs, escaped
|
|
# names, wrappers, separators and the overflow note (range: 512-65536).
|
|
# Usually binds before the entry cap for CJK document names.
|
|
shelf_index_max_bytes: 4096
|
|
# Days a trashed project document stays recoverable before the retention
|
|
# sweep may purge it permanently (range: 1-3650).
|
|
trash_retention_days: 30
|
|
|
|
# ============================================================================
|
|
# Provider Safety Termination Configuration
|
|
# ============================================================================
|
|
# Intercept AIMessages where the provider stopped generation for safety reasons
|
|
# (e.g. OpenAI finish_reason='content_filter', Anthropic stop_reason='refusal',
|
|
# Gemini finish_reason='SAFETY') while still returning tool_calls. The
|
|
# tool_calls in such responses are typically truncated/unreliable and must
|
|
# not be executed. See issue #3028 for the full failure mode.
|
|
#
|
|
# Detectors are loaded by class path via reflection (same pattern as
|
|
# guardrails / models / tools). The built-in set covers OpenAI-compatible
|
|
# content_filter, Anthropic refusal, and Gemini SAFETY/BLOCKLIST/
|
|
# PROHIBITED_CONTENT/SPII/RECITATION.
|
|
|
|
safety_finish_reason:
|
|
enabled: true
|
|
# Leave `detectors` unset to use the built-in detector set. Set to a
|
|
# non-empty list to fully override (use `enabled: false` to disable instead
|
|
# of providing an empty list).
|
|
#
|
|
# Example — extend the OpenAI-compatible detector for a Chinese provider
|
|
# whose gateway uses a non-standard finish_reason token:
|
|
# detectors:
|
|
# - use: deerflow.agents.middlewares.safety_termination_detectors:OpenAICompatibleContentFilterDetector
|
|
# config:
|
|
# finish_reasons: ["content_filter", "sensitive", "risk_control"]
|
|
# - use: deerflow.agents.middlewares.safety_termination_detectors:AnthropicRefusalDetector
|
|
# - use: deerflow.agents.middlewares.safety_termination_detectors:GeminiSafetyDetector
|
|
#
|
|
# Example — add a custom detector for an in-house provider:
|
|
# detectors:
|
|
# - use: my_company.deerflow_ext:WenxinSafetyDetector
|
|
# config:
|
|
# error_codes: [336003, 17, 18]
|
|
|
|
# ============================================================================
|
|
# Uploads
|
|
# ============================================================================
|
|
# Limits and conversion settings for files attached to a conversation.
|
|
uploads:
|
|
# Application-level upload limits enforced by the gateway and exposed to the
|
|
# frontend before file selection.
|
|
max_files: 10
|
|
max_file_size: 52428800 # 50 MiB
|
|
max_total_size: 104857600 # 100 MiB
|
|
# Automatic Office/PDF conversion runs on the backend host before sandbox
|
|
# isolation applies. Keep this disabled unless uploads come from a fully
|
|
# trusted source and you intentionally accept host-side parser risk.
|
|
auto_convert_documents: false
|
|
# Controls which PDF-to-Markdown converter is used whenever PDF conversion
|
|
# runs. Automatic upload conversion is gated separately by
|
|
# auto_convert_documents.
|
|
# auto — prefer pymupdf4llm when installed; fall back to MarkItDown for
|
|
# image-based or encrypted PDFs (recommended default).
|
|
# pymupdf4llm — always use pymupdf4llm (must be installed: uv add pymupdf4llm).
|
|
# Better heading/table extraction; faster on most files.
|
|
# markitdown — always use MarkItDown (original behaviour, no extra dependency).
|
|
pdf_converter: auto
|
|
|
|
# ============================================================================
|
|
# Sandbox Configuration
|
|
# ============================================================================
|
|
# Choose between local sandbox (direct execution), Docker-based AIO sandbox,
|
|
# or BoxLite micro-VM sandbox
|
|
# Option 1: Local Sandbox (Default)
|
|
# Executes commands directly on the host machine
|
|
sandbox:
|
|
use: deerflow.sandbox.local:LocalSandboxProvider
|
|
# Host bash execution is disabled by default because LocalSandboxProvider is
|
|
# not a secure isolation boundary for shell access. Enable only for fully
|
|
# trusted, single-user local workflows.
|
|
allow_host_bash: false
|
|
# Optional: Mount additional host directories into the sandbox.
|
|
# Each mount maps a host path to a virtual container path accessible by the agent.
|
|
# Note: with LocalSandboxProvider under `make up` (docker-compose), host_path is
|
|
# checked from inside the deer-flow-gateway container — you must also bind-mount
|
|
# the same directory into services.gateway.volumes in docker/docker-compose.yaml
|
|
# for this mount to take effect (see issue #3244).
|
|
# mounts:
|
|
# - host_path: /home/user/my-project # Absolute path; see note above for Docker mode
|
|
# container_path: /mnt/my-project # Virtual path inside the sandbox
|
|
# read_only: true # Whether the mount is read-only (default: false)
|
|
|
|
# Tool output truncation limits (characters).
|
|
# bash uses middle-truncation (head + tail) since errors can appear anywhere in the output.
|
|
# read_file and ls use head-truncation since their content is front-loaded.
|
|
# A read_file cut lands on a line boundary when the budget allows, and the marker names
|
|
# the next start_line to continue with, so the agent can page through a long file exactly.
|
|
# Set to 0 to disable truncation.
|
|
bash_output_max_chars: 20000
|
|
read_file_output_max_chars: 50000
|
|
ls_output_max_chars: 20000
|
|
|
|
# Provider command deadline (default: 600). AIO images on the supported
|
|
# semver line (1.9.3+, recommended 1.11.0) enforce it server-side through
|
|
# `hard_timeout`; the frozen legacy `all-in-one-sandbox:latest` image only
|
|
# gets the bounded host-side request. `bash_command_timeout` is used by
|
|
# providers that explicitly wire this setting (currently LocalSandbox,
|
|
# AioSandbox, and OpenSandbox). Other providers retain their provider-specific
|
|
# command defaults unless a caller supplies an explicit timeout. For providers
|
|
# that enforce this deadline server- or process-side, blocking foreground
|
|
# commands are terminated at the configured deadline. Long-lived processes
|
|
# should be started in the background with output redirected; unredirected
|
|
# background output is drained with bounded capture.
|
|
bash_command_timeout: 600
|
|
|
|
# Option 2: Container-based AIO Sandbox
|
|
# Executes commands in isolated containers (Docker or Apple Container)
|
|
# On macOS: Prefers Apple Container when available, but keeps Docker while this
|
|
# sandbox prefix still has managed Docker containers to reconcile after a mode change
|
|
# On other platforms: Uses Docker
|
|
# Uncomment to use:
|
|
# sandbox:
|
|
# use: deerflow.community.aio_sandbox:AioSandboxProvider
|
|
#
|
|
# # Optional: Container image to use (works with both Docker and Apple Container)
|
|
# # Default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
|
|
# # The mirror's `:latest` tag is frozen on an old pre-1.9.3 digest that lacks
|
|
# # the /v1/bash/* routes required-secrets skills need (see #3921/#3922), so
|
|
# # pin an explicit version instead — recommended: 1.11.0 (multi-arch, works
|
|
# # on both x86_64 and arm64). Custom images should extend the default image
|
|
# # or implement the same AIO sandbox HTTP API used by agent-sandbox. See
|
|
# # backend/docs/CONFIGURATION.md.
|
|
# # image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0
|
|
#
|
|
# # Optional: Base port for sandbox containers (default: 8080)
|
|
# # port: 8080
|
|
|
|
# # Optional: Maximum number of concurrent sandbox containers (default: 3)
|
|
# # When the limit is reached the least-recently-used sandbox is evicted to
|
|
# # make room for new ones. Use a positive integer here; omit this field to use the default.
|
|
# # replicas: 3
|
|
#
|
|
# # Optional: Prefix for container names (default: deer-flow-sandbox)
|
|
# # container_prefix: deer-flow-sandbox
|
|
#
|
|
# # Optional: outbound network policy for locally managed Docker sandboxes.
|
|
# # open keeps the current behavior. isolated denies all outbound traffic.
|
|
# # allowlist permits the listed HTTP/HTTPS domains; denied public domains can
|
|
# # optionally be approved from the Human Input card. Restricted modes require
|
|
# # Docker Engine 28+ and are not supported by Apple Container or provisioner mode.
|
|
# # Private, loopback, link-local, multicast, and cloud metadata addresses are always denied.
|
|
# network:
|
|
# mode: allowlist # open | isolated | allowlist (default: open)
|
|
# allow_domains:
|
|
# - pypi.org
|
|
# - files.pythonhosted.org
|
|
# - registry.npmjs.org
|
|
# - github.com
|
|
# approval: prompt # deny | prompt
|
|
# temporary_grant_ttl: 300 # 30-3600 seconds
|
|
# proxy_image: ghcr.io/bytedance/deer-flow-sandbox-network-proxy:latest
|
|
#
|
|
# # Optional: Override whether the sandbox already sees the gateway's
|
|
# # thread workspace/uploads/outputs through shared mounts.
|
|
# # Omit this field to auto-detect from the backend (local containers: true;
|
|
# # remote/provisioner backends: false). Set true only when the deployment
|
|
# # guarantees both sides use the same thread user-data directories, such as
|
|
# # a correctly aligned shared PVC, NFS volume, hostPath, or bind mount.
|
|
# # true skips per-upload sandbox acquire/sync; false forces explicit sync.
|
|
# # thread_data_mounts: true
|
|
#
|
|
# # Optional: Additional mount directories from host to container
|
|
# # NOTE: Skills directory is automatically mounted from skills.path to skills.container_path
|
|
# # mounts:
|
|
# # # Other custom mounts
|
|
# # - host_path: /path/on/host
|
|
# # container_path: /home/user/shared
|
|
# # read_only: false
|
|
# #
|
|
# # # DeerFlow will surface configured container_path values to the agent,
|
|
# # # so it can directly read/write mounted directories such as /home/user/shared
|
|
#
|
|
# # Optional: Environment variables to inject into the sandbox container
|
|
# # Values starting with $ will be resolved from host environment variables
|
|
# # AIO semver images default MAX_SHELL_SESSIONS to 10. DeerFlow leaves that
|
|
# # image default unchanged while subagent_runtime.max_running + 1 fits, and
|
|
# # otherwise injects the required value for local and provisioner sandboxes.
|
|
# # An explicit value is allowed but must be at least max_running + 1.
|
|
# # environment:
|
|
# # MAX_SHELL_SESSIONS: "13" # Example for subagent_runtime.max_running: 12
|
|
# # NODE_ENV: production
|
|
# # DEBUG: "false"
|
|
# # API_KEY: $MY_API_KEY # Reads from host's MY_API_KEY env var
|
|
# # DATABASE_URL: $DATABASE_URL # Reads from host's DATABASE_URL env var
|
|
# # # Required when using the OpenAI-compatible image-generation provider:
|
|
# # IMAGE_GENERATION_PROVIDER: $IMAGE_GENERATION_PROVIDER
|
|
# # IMAGE_GENERATION_API_KEY: $IMAGE_GENERATION_API_KEY
|
|
# # IMAGE_GENERATION_BASE_URL: $IMAGE_GENERATION_BASE_URL
|
|
# # IMAGE_GENERATION_MODEL: $IMAGE_GENERATION_MODEL
|
|
# # IMAGE_GENERATION_SIZE: $IMAGE_GENERATION_SIZE # Optional fixed-size override
|
|
#
|
|
# # Optional: Cross-instance container ownership (issue #4206).
|
|
# #
|
|
# # Gateway instances share sandbox containers but each keeps its own in-memory
|
|
# # warm pool. Without shared ownership state, one instance's startup
|
|
# # reconciliation adopts a container another instance is actively using and
|
|
# # later idle-destroys it — tool calls then fail with 502 / connection refused.
|
|
# #
|
|
# # Single gateway instance? Leave this out; `memory` is the default and the
|
|
# # cross-instance kill cannot happen.
|
|
# #
|
|
# # MULTIPLE gateway instances / workers sharing one container backend
|
|
# # (load-balanced deployments, Docker Compose) MUST set type: redis. Docker
|
|
# # Compose already sets DEER_FLOW_STREAM_BRIDGE_REDIS_URL, which is taken as
|
|
# # proof the deployment is multi-instance, so redis ownership is inferred even
|
|
# # if this section is omitted.
|
|
# #
|
|
# # The redis ownership store requires the optional `redis` extra. It is
|
|
# # auto-detected from this section on `make dev` and always installed in the
|
|
# # Docker image. To install it manually:
|
|
# # cd backend && uv sync --all-packages --extra redis
|
|
# #
|
|
# # ownership:
|
|
# # type: memory # single gateway instance only
|
|
# #
|
|
# # ownership:
|
|
# # type: redis # required for multi-instance / load-balanced
|
|
# # redis_url: redis://redis:6379/0
|
|
# # renewal_interval_seconds: 30 # how often an owner refreshes its leases
|
|
# # ttl_multiplier: 4 # lease TTL = interval x this (min 2, so a
|
|
# # # single missed renewal cannot expire a live
|
|
# # # owner). Liveness is deliberately independent
|
|
# # # of idle_timeout: renewal keeps running even
|
|
# # # at idle_timeout: 0.
|
|
# # key_prefix: deerflow:sandbox:owner
|
|
# #
|
|
# # NOTE: the redis ownership store is fail-closed, matching the stream bridge's
|
|
# # fail-hard policy. Redis.from_url is lazy so a down Redis does not block
|
|
# # startup, but a sandbox whose ownership cannot be published is not handed out
|
|
# # — acquiring raises instead. The alternative (proceed unowned) is exactly the
|
|
# # #4206 cross-instance kill. Run Redis with HA / a restart policy.
|
|
# #
|
|
# # NOTE: the other boundary is the lease TTL (renewal_interval_seconds x
|
|
# # ttl_multiplier). A Redis outage longer than the TTL can let a reconciling
|
|
# # instance adopt a live owner's container: a lapsed lease is indistinguishable
|
|
# # from a dead owner, so one TTL of adoption grace is the whole safety margin.
|
|
# # Size the TTL against your Redis availability target (HA Redis keeps this
|
|
# # window out of reach).
|
|
|
|
# Option 3: BoxLite micro-VM Sandbox
|
|
# Runs each sandbox as a BoxLite micro-VM. Released boxes stay in an in-process
|
|
# warm pool and can be reclaimed by the same user/thread without a cold start.
|
|
# Requires the boxlite runtime and host virtualization support (KVM on Linux,
|
|
# Hypervisor.framework on macOS).
|
|
# sandbox:
|
|
# use: deerflow.community.boxlite:BoxliteProvider
|
|
# image: python:3.12-slim
|
|
#
|
|
# # Optional: Per-box memory and CPU limits.
|
|
# # memory_mib: 1024
|
|
# # cpus: 2
|
|
#
|
|
# # Optional: Maximum active + warm BoxLite VMs per gateway process (default: 3).
|
|
# # Active boxes are never evicted; only warm-pool boxes are stopped to make room.
|
|
# # replicas: 3
|
|
#
|
|
# # Optional: Seconds before an idle warm-pool VM is stopped (default: 600).
|
|
# # Set to 0 to keep warm VMs until shutdown or replica eviction.
|
|
# # idle_timeout: 600
|
|
#
|
|
# # Optional: Skip the reclaim health check for very recently released VMs.
|
|
# # Default 0.0 keeps reliability-first validation before warm reuse.
|
|
# # health_check_skip_seconds: 0.0
|
|
#
|
|
# # Optional: Environment variables to inject into every command.
|
|
# # environment:
|
|
# # PYTHONUNBUFFERED: "1"
|
|
#
|
|
# Option 4: Provisioner-managed AIO Sandbox (docker-compose-dev)
|
|
# Each sandbox_id gets a dedicated Pod in k3s, managed by the provisioner.
|
|
# Recommended for production or advanced users who want better isolation and scalability.:
|
|
# sandbox:
|
|
# use: deerflow.community.aio_sandbox:AioSandboxProvider
|
|
# provisioner_url: http://provisioner:8002
|
|
# # API key for provisioner authentication. Must match PROVISIONER_API_KEY
|
|
# # set on the provisioner container. Both sides must have the same value set;
|
|
# # the provisioner rejects all /api/* requests when PROVISIONER_API_KEY is unset.
|
|
# # Generate a strong key: openssl rand -hex 32
|
|
# # provisioner_api_key: $PROVISIONER_API_KEY
|
|
# # Note: provisioner-created Pods use the provisioner's SANDBOX_IMAGE
|
|
# # environment variable, not sandbox.image from this config file.
|
|
|
|
# Option 5: Tenki cloud microVM Sandbox
|
|
# Runs each sandbox as an isolated Tenki cloud microVM. Released sandboxes stay
|
|
# in an in-process warm pool and are reclaimed by the same user/thread without a
|
|
# cold start. Requires the optional SDK: pip install "deerflow-harness[tenki]".
|
|
# sandbox:
|
|
# use: deerflow.community.tenki:TenkiSandboxProvider
|
|
# # api_key: $TENKI_API_KEY # falls back to TENKI_API_KEY / TENKI_AUTH_TOKEN env var
|
|
# # base_url: https://tenki.cloud # optional; SDK default when omitted
|
|
# # image: my-base-image # optional; Tenki account default base image when omitted
|
|
# # workspace_id: ws_... # optional; auto-selected if the account has exactly one
|
|
# # Migration: sandbox.project_id is gone — Tenki 1.x removed projects, so scope
|
|
# # is the workspace alone. Set workspace_id if the account has more than one.
|
|
# # A leftover project_id is ignored and logs a warning at startup.
|
|
# # cpu_cores: 2 # optional per-sandbox vCPUs
|
|
# # memory_mb: 2048 # optional per-sandbox memory
|
|
# # replicas: 3 # active + warm microVM cap per gateway process
|
|
# # idle_timeout: 600 # warm microVM idle seconds before terminate; 0 disables
|
|
# # max_duration: 14400 # Tenki sandbox lifetime in seconds; 0 uses the account default
|
|
# # sticky: false # pin the microVM to its host (only matters with pause/resume)
|
|
# # home_dir: /home/tenki # writable dir backing /mnt/user-data
|
|
# # environment: # injected into every command (and as create-time env)
|
|
# # PYTHONUNBUFFERED: "1"
|
|
|
|
# Option 6: OpenSandbox remote Sandbox
|
|
# Runs each sandbox through an OpenSandbox server. Released sandboxes stay in an
|
|
# in-process warm pool and are reclaimed only by the same user/thread. Requires
|
|
# the optional SDK: pip install "deerflow-harness[opensandbox]".
|
|
# sandbox:
|
|
# use: deerflow.community.opensandbox:OpenSandboxProvider
|
|
# image: python:3.11
|
|
# # api_key: $OPEN_SANDBOX_API_KEY # optional when the SDK env var is set
|
|
# # domain: localhost:8080 # optional; OPEN_SANDBOX_DOMAIN fallback
|
|
# # protocol: http # localhost only; use https for remote domains
|
|
# # request_timeout: 30 # finite positive management API request timeout seconds
|
|
# # ready_timeout: 30 # finite positive create/readiness timeout seconds
|
|
# # use_server_proxy: false # proxy execd/file traffic through server
|
|
# # sandbox_timeout: 14400 # finite non-negative remote lifetime seconds; 0 = explicit cleanup
|
|
# # bash_command_timeout: 600 # default remote command timeout seconds
|
|
# # replicas: 3 # active + warm cap per gateway process
|
|
# # idle_timeout: 600 # warm seconds before destroy; 0 disables
|
|
# # environment: # create-time and per-command defaults
|
|
# # PYTHONUNBUFFERED: "1"
|
|
|
|
# ============================================================================
|
|
# Subagents Configuration
|
|
# ============================================================================
|
|
# Configure timeouts for subagent execution
|
|
# Subagents are background workers delegated tasks by the lead agent
|
|
|
|
# Process-wide execution capacity. These fields are restart-required and are
|
|
# shared by ordinary `task` calls and durable batches. Waiting work is async;
|
|
# queued subagents do not occupy a scheduler thread.
|
|
subagent_runtime:
|
|
max_running: 3
|
|
max_queued: 64
|
|
admission_policy: queue # queue or reject when all slots are occupied
|
|
queue_timeout_seconds: 300
|
|
|
|
# subagents:
|
|
# # Default timeout (seconds) for built-in subagents (default: 1800 = 30 min).
|
|
# # Custom agents use their own timeout_seconds (default 900) unless overridden.
|
|
# timeout_seconds: 1800
|
|
# # Optional global max-turn override for all subagents. One turn is one
|
|
# # model call plus the tools it runs; the turn count is converted into
|
|
# # LangGraph's step budget, so this is the number of turns you actually get.
|
|
# # Built-in defaults: general-purpose=150, bash=60. Leave unset to keep them.
|
|
# # max_turns: 120
|
|
#
|
|
# # Total number of subagent delegations allowed in one lead-agent run.
|
|
# # This is a deterministic backstop against repeated planning checkpoints
|
|
# # launching legal-sized batches forever. The default 6 allows two full
|
|
# # batches at the default concurrency of 3. Valid config range: 1-50.
|
|
# # Per-request runtime context can temporarily override this with
|
|
# # `max_total_subagents`, clamped to the same 1-50 range.
|
|
# max_total_per_run: 6
|
|
#
|
|
# # Per-run token ceiling for subagents (#3875 Phase 2). A backstop against a
|
|
# # subagent that burns tokens on trivial work. At the hard-stop threshold the
|
|
# # in-flight turn is capped (tool calls stripped, finish_reason forced to
|
|
# # "stop") so the run completes naturally with a final answer; the result is
|
|
# # stamped `completed` + `subagent_stop_reason=token_capped` so the lead and
|
|
# # UI can tell a budget-capped completion from a clean one. The 2,000,000
|
|
# # default is a generous ceiling — lower it to tighten cost controls. A
|
|
# # per-agent `token_budget` override (see `agents:` below) wins over this.
|
|
# # token_budget:
|
|
# # enabled: true
|
|
# # max_tokens: 2000000
|
|
# # warn_threshold: 0.7 # log a warning once this fraction of the budget is spent
|
|
#
|
|
# # Optional per-agent overrides (applies to both built-in and custom agents)
|
|
# agents:
|
|
# general-purpose:
|
|
# timeout_seconds: 2700 # 45 minutes for very long deep-research tasks
|
|
# max_turns: 250 # raise above the 150 default for very deep tasks
|
|
# # token_budget: # per-agent override of the global token_budget above
|
|
# # max_tokens: 3000000 # raise the ceiling for deep-research tasks
|
|
# # model: qwen3:32b # Use a specific model (default: inherit from lead agent)
|
|
# # skills: # Skill discovery/activation allowlist (default: all enabled)
|
|
# # - web-search
|
|
# # - data-analysis
|
|
# bash:
|
|
# timeout_seconds: 300 # 5 minutes for quick command execution
|
|
# max_turns: 80
|
|
# # skills: [] # No discoverable/activatable skills for bash agent
|
|
#
|
|
# # Custom subagent types: define specialized agents with their own prompts,
|
|
# # tools, skills, and model configuration. Custom agents are available via
|
|
# # the `task` tool alongside built-in types (general-purpose, bash).
|
|
# # custom_agents:
|
|
# # analysis:
|
|
# # description: "Data analysis specialist for processing datasets and generating insights"
|
|
# # system_prompt: |
|
|
# # You are a data analysis subagent. Focus on:
|
|
# # - Processing and analyzing datasets
|
|
# # - Generating visualizations
|
|
# # - Providing statistical insights
|
|
# # tools: # Tool whitelist (null = inherit all)
|
|
# # - bash
|
|
# # - read_file
|
|
# # - write_file
|
|
# # skills: # Skill discovery/activation allowlist (null = all, [] = none)
|
|
# # - data-analysis
|
|
# # - visualization
|
|
# # model: inherit # 'inherit' uses parent's model
|
|
# # max_turns: 80
|
|
# # timeout_seconds: 600
|
|
#
|
|
# # Model override: by default, subagents inherit the lead agent's model.
|
|
# # Set `model` to use a different model (e.g., a local Ollama model for cost savings).
|
|
# # The model name must match a name defined in the `models:` section above.
|
|
|
|
# Durable native-subagent batches. Disabled by default because enabling it can
|
|
# materially increase model usage and requires database.backend sqlite/postgres.
|
|
# The three limits are intentionally separate:
|
|
# - total: all persisted items in one batch
|
|
# - live: pending work admitted as queued/running at one time
|
|
# - running: items from one batch allowed to hold real execution slots
|
|
subagent_batches:
|
|
enabled: false
|
|
poll_interval_seconds: 1
|
|
lease_seconds: 120
|
|
max_items_per_batch: 5000
|
|
default_max_live_items: 100
|
|
max_live_items_per_batch: 1000
|
|
default_max_running_items: 3
|
|
max_running_items_per_batch: 64
|
|
max_attempts: 3
|
|
max_result_chars: 100000
|
|
result_preview_max_chars: 2000
|
|
|
|
# ============================================================================
|
|
# Tool Result Verification
|
|
# ============================================================================
|
|
# Deterministic receipts are stamped onto tool results and injected into the
|
|
# model context so final reports can cite executed actions. Disable them only
|
|
# if the extra provenance context is not wanted. The selective judge settings
|
|
# are reserved for acceptance-criteria review and remain off by default.
|
|
verification:
|
|
receipts_enabled: true
|
|
# Lead-chain ledger rendering: 'delegation_only' renders only while
|
|
# processing subagent results (subagent chains always render).
|
|
receipts_render_mode: "delegation_only"
|
|
judge_enabled: false
|
|
judge_model_name: null
|
|
|
|
# ============================================================================
|
|
# ACP Agents Configuration
|
|
# ============================================================================
|
|
# Configure external ACP-compatible agents for the built-in `invoke_acp_agent` tool.
|
|
|
|
# acp_agents:
|
|
# mcode:
|
|
# # MiniMax Code speaks ACP directly; no adapter package is required.
|
|
# # Install with `npm install --global @minimax-ai/code`, then run `mcode login`.
|
|
# command: mcode
|
|
# args: ["acp"]
|
|
# description: MiniMax Code for implementation, refactoring, debugging, and repository tasks
|
|
# # auto_approve_permissions: false # Enable only for trusted tasks that need MCode to edit files or run commands
|
|
# # timeout_seconds: 1800 # Abort + kill the subprocess if it doesn't respond in time (default: 1800 = 30 min)
|
|
#
|
|
# claude_code:
|
|
# # DeerFlow expects an ACP adapter here. The standard `claude` CLI does not
|
|
# # speak ACP directly. Install `claude-agent-acp` separately or use:
|
|
# command: npx
|
|
# args: ["-y", "@zed-industries/claude-agent-acp"]
|
|
# description: Claude Code for implementation, refactoring, and debugging
|
|
# model: null
|
|
# # auto_approve_permissions: false # Set to true to auto-approve ACP permission requests
|
|
# # timeout_seconds: 1800 # Abort + kill the subprocess if it doesn't respond in time (default: 1800 = 30 min)
|
|
# # env: # Optional: inject environment variables into the agent subprocess
|
|
# # ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY # $VAR resolves from host environment
|
|
#
|
|
# codex:
|
|
# # DeerFlow expects an ACP adapter here. The standard `codex` CLI does not
|
|
# # speak ACP directly. Install `codex-acp` separately or use:
|
|
# command: npx
|
|
# args: ["-y", "@zed-industries/codex-acp"]
|
|
# description: Codex CLI for repository tasks and code generation
|
|
# model: null
|
|
# # auto_approve_permissions: false # Set to true to auto-approve ACP permission requests
|
|
# # env: # Optional: inject environment variables into the agent subprocess
|
|
# # OPENAI_API_KEY: $OPENAI_API_KEY # $VAR resolves from host environment
|
|
|
|
# ============================================================================
|
|
# Skills Configuration
|
|
# ============================================================================
|
|
# Configure skills directory for specialized agent workflows
|
|
|
|
skills:
|
|
# Path to skills directory on the host (relative to project root or absolute)
|
|
# Default: skills under the project root
|
|
# Override with DEER_FLOW_SKILLS_PATH when this field is omitted.
|
|
# Uncomment to customize:
|
|
# path: /absolute/path/to/custom/skills
|
|
|
|
# Path where skills are mounted in the sandbox container
|
|
# This is used by the agent to access skills in both local and Docker sandbox
|
|
# AIO/provisioner and E2B modes require a canonical absolute non-root path
|
|
# that does not overlap reserved platform mounts. Providers snapshot this
|
|
# path for identity, mounts, metadata, and synchronization, so restart the
|
|
# Gateway after changing it.
|
|
# Default: /mnt/skills
|
|
container_path: /mnt/skills
|
|
|
|
# Deferred skill discovery (default: false)
|
|
# When enabled, only skill names appear in the system prompt (<skill_index>).
|
|
# The LLM discovers skill details on demand via the describe_skill tool.
|
|
# This keeps the system prompt compact and prefix-cache friendly when many
|
|
# skills are installed.
|
|
# deferred_discovery: true
|
|
|
|
# ============================================================================
|
|
# SkillScan Configuration
|
|
# ============================================================================
|
|
# Native deterministic skill safety scanning. This runs before the LLM skill
|
|
# scanner on skill install/update and agent-managed skill writes.
|
|
skill_scan:
|
|
# Set false to disable the new deterministic analyzers (nested-archive,
|
|
# secret-pattern, and other content-level checks). Safe archive extraction
|
|
# (path traversal, symlinks, executable-binary, total-size, and entry-count
|
|
# limits) and the LLM skill scanner still run unconditionally.
|
|
enabled: true
|
|
|
|
# Note: To restrict which skills are loaded for a specific custom agent,
|
|
# define a `skills` list in that agent's `config.yaml` (e.g. `agents/my-agent/config.yaml`):
|
|
# - Omitted or null: load all globally enabled skills (default)
|
|
# - []: disable all skills for this agent
|
|
# - ["skill-name"]: load only specific skills
|
|
|
|
# ============================================================================
|
|
# Title Generation Configuration
|
|
# ============================================================================
|
|
# Automatic conversation title generation settings
|
|
|
|
title:
|
|
enabled: true
|
|
max_words: 6
|
|
max_chars: 60
|
|
model_name: null # null = fast local fallback; set a model name to use LLM title generation
|
|
|
|
# ============================================================================
|
|
# Summarization Configuration
|
|
# ============================================================================
|
|
# Automatically summarize conversation history when token limits are approached
|
|
# This helps maintain context in long conversations without exceeding model limits
|
|
|
|
# Optional task notes and keyword recall of compacted messages. See docs/task-continuity.md.
|
|
task_continuity:
|
|
enabled: false
|
|
max_batches: 32
|
|
max_records_per_batch: 256
|
|
max_record_chars: 16000
|
|
|
|
summarization:
|
|
enabled: true
|
|
|
|
# Model to use for summarization.
|
|
# null = summarize with the model the run actually uses (the lead run's model, a
|
|
# subagent's own model, or a thread's custom-agent model), NOT models[0].
|
|
# set = that model generates; if its provider fails, compaction falls back to the
|
|
# run's own model so a broken summary provider cannot disable compaction.
|
|
# Recommended: Use a lightweight, cost-effective model like "gpt-4o-mini" or similar
|
|
model_name: null
|
|
|
|
# Trigger conditions - at least one required
|
|
# Summarization runs when ANY threshold is met (OR logic)
|
|
# You can specify a single trigger or a list of triggers
|
|
trigger:
|
|
# Trigger when token count reaches 32000
|
|
- type: tokens
|
|
value: 32000
|
|
# Uncomment to also trigger when message count reaches 50
|
|
# - type: messages
|
|
# value: 50
|
|
# Uncomment to trigger when 80% of model's max input tokens is reached.
|
|
# The percentage resolves from the SUMMARY model's declared `context_window`
|
|
# (summarization.model_name when set, else the run's own model): declare it
|
|
# on that models entry — third-party OpenAI-compatible models carry no
|
|
# built-in profile. Without it the fraction clause is dropped with a warning
|
|
# and any remaining absolute clauses keep working. If a separate summary
|
|
# model with a larger window is configured, prefer absolute `tokens`
|
|
# thresholds sized for the run model instead.
|
|
# - type: fraction
|
|
# value: 0.8
|
|
|
|
# Context retention policy after summarization
|
|
# Specifies how much recent history to preserve
|
|
keep:
|
|
# Keep the most recent 10 messages (recommended)
|
|
type: messages
|
|
value: 10
|
|
# Alternative: Keep specific token count
|
|
# type: tokens
|
|
# value: 3000
|
|
# Alternative: Keep percentage of model's max input tokens
|
|
# type: fraction
|
|
# value: 0.3
|
|
|
|
# Maximum tokens to keep when preparing messages for summarization
|
|
# Set to null to skip trimming (not recommended for very long conversations)
|
|
trim_tokens_to_summarize: 15564
|
|
|
|
# Custom summary prompt template (null = use default LangChain prompt)
|
|
# The prompt should guide the model to extract important context
|
|
summary_prompt: null
|
|
|
|
# Loaded SKILL.md references (read_file calls under skills.container_path) are
|
|
# captured into the durable skill_context channel and re-injected after
|
|
# compaction as name/path/description reminders. The full skill body is not
|
|
# persisted; the agent should re-read the file before applying instructions.
|
|
# Tool names counted as skill reads:
|
|
# Legacy preserve_recent_skill_* summarization settings are no longer used;
|
|
# skill retention is handled by this durable reference channel instead. Set
|
|
# this list to [] to disable durable skill-reference capture.
|
|
skill_file_read_tool_names:
|
|
- read_file
|
|
- read
|
|
- view
|
|
- cat
|
|
|
|
# ============================================================================
|
|
# Memory Configuration
|
|
# ============================================================================
|
|
# Global memory mechanism (pluggable + self-contained).
|
|
#
|
|
# Shared fields (host level, backend-agnostic):
|
|
# enabled - Master switch for the memory mechanism (call-site gate)
|
|
# injection_enabled - Whether to inject memory into the system prompt (call-site gate)
|
|
# shutdown_flush_timeout_seconds - Hard budget (s) to drain pending updates on Gateway graceful shutdown (default: 30)
|
|
# manager_class - Backend selector: registered name (deermem/mem0/noop/openviking) or dotted path
|
|
# backend_config - Backend-private config dict (passthrough; each backend self-interprets)
|
|
#
|
|
# DeerMem-private fields live under ``backend_config`` (NOT at the memory: top level):
|
|
# storage_path - Data root. Empty = deer-flow base_dir (factory injects absolute
|
|
# runtime_home). Absolute path = that root. Relative = CWD-relative.
|
|
# model - LLM config for memory extraction: {provider, model, api_key, base_url,
|
|
# temperature}. Omit all fields = no extraction (non-LLM ops still work).
|
|
# debounce_seconds - Debounce wait before processing queued updates (default: 30)
|
|
# max_facts - Maximum facts to store (default: 100)
|
|
# fact_confidence_threshold - Minimum confidence for storing facts (default: 0.7)
|
|
# max_injection_tokens - Token budget for memory injection (default: 2000)
|
|
# token_counting - tiktoken (accurate, network on first use) or char (network-free)
|
|
# guaranteed_categories - Fact categories always injected (default: ["correction"])
|
|
# guaranteed_token_budget - Token ceiling for guaranteed categories (default: 500)
|
|
# staleness_review_enabled - Enable staleness pruning (default: true)
|
|
# staleness_age_days - Age threshold for staleness candidates (default: 90)
|
|
# staleness_min_candidates - Minimum stale facts to trigger review (default: 3)
|
|
# staleness_max_removals_per_cycle - Safety cap on removals per cycle (default: 10)
|
|
# staleness_protected_categories - Categories exempt from staleness review (default: ["correction"])
|
|
# staleness_max_lifetime_multiplier - Creation-time cap multiplier for a fact LLM-assigned
|
|
# expected_valid_days; new facts clamped to
|
|
# staleness_age_days x multiplier. Default 20.0
|
|
# (90 x 20 = 1800d ~ 5 years) supports the very-stable
|
|
# prompt tier. (default: 20.0, range: 1.0-100.0)
|
|
# staleness_max_extension_days - Absolute ceiling (in days) on expected_valid_days after a
|
|
# lifetime extension (staleFactsToExtend). Prevents timedelta
|
|
# overflow and LLM misfire from permanently deferring a fact.
|
|
# (default: 3650, range: 90-36500)
|
|
memory:
|
|
enabled: true
|
|
injection_enabled: true # Whether to inject memory into system prompt
|
|
# Hard budget (seconds) to drain pending memory updates on Gateway graceful
|
|
# shutdown. Each pending item does one LLM call, so large IM batches may need
|
|
# more. Must fit inside the pod's K8s terminationGracePeriodSeconds (channel
|
|
# stop + this drain + buffer) or K8s SIGKILLs the drain -- set that on the
|
|
# gateway Helm deployment (see deploy/helm/deer-flow). Default 30s.
|
|
shutdown_flush_timeout_seconds: 30.0
|
|
# Memory backend selector. Either a registered backend name (matching a
|
|
# backends/<name>/ folder that exposes MANAGER_CLASS, e.g. deermem / mem0 / noop)
|
|
# or a dotted import path to a MemoryManager subclass.
|
|
manager_class: deermem
|
|
# Memory operation mode:
|
|
# middleware (default) - passive background extraction after each turn.
|
|
# tool - experimental opt-in; the model calls memory_search/memory_add/
|
|
# memory_update/memory_delete directly. This gives the model agency over
|
|
# memory writes, but effectiveness depends on model tool-use behavior.
|
|
# Normally only one mode runs at a time. A backend that needs conversation-
|
|
# level extraction may retain passive writes in tool mode while still
|
|
# exposing query-aware search (mem0 does this). Tool calls go through the
|
|
# active MemoryManager; unsupported fact CRUD returns a JSON error.
|
|
mode: middleware
|
|
# Pre-extraction cost gate (optional, OFF by default). Decides whether a batch
|
|
# of new conversation is worth paying for the extraction LLM call, using one
|
|
# `noul` question ("does this contain durable, cross-task, user-level
|
|
# information?").
|
|
# off (default) - no request, no behavior change.
|
|
# shadow - decide and record the verdict, still extract (the safe first step).
|
|
# enforce - a low-probability batch skips the call AND advances the watermark,
|
|
# so that batch is consumed as "nothing durable here". Enable only after the
|
|
# measurement gate that scripts/eval_memory_prescreen.py implements and
|
|
# backend/docs/MEMORY_IMPROVEMENTS.md documents
|
|
# passes; `skip_threshold` below is provisional and unmeasured.
|
|
# The failure direction is always extraction: an error, timeout, unusable
|
|
# response, missing verdict, or an over-limit batch extracts as usual. A skip is
|
|
# not a safety boundary, gates no execution, and changes no post-extraction
|
|
# gate (confidence / scope / durability / authority / dedup / eviction).
|
|
#
|
|
# EGRESS: the formatted conversation text (user + final assistant turns, which
|
|
# may contain file paths or credentials) is sent to the TypeSafe API -- a second
|
|
# destination beyond your own extraction model. Existing memory is never sent.
|
|
# `pii_redaction_middleware` does not cover this path.
|
|
#
|
|
# NOTE: a batch is not judged at all while `staleness_review_enabled` (true
|
|
# below) or `consolidation_enabled` is on -- a skip would also skip that batch's
|
|
# maintenance review -- and never on the emergency (pre-summarization) flush or
|
|
# the shutdown drain. With the defaults shipped here, shadow/enforce therefore
|
|
# sees no batches until staleness_review_enabled is turned off.
|
|
# prescreen:
|
|
# mode: "off"
|
|
# use: deerflow.agents.memory.prescreen.typesafe:TypeSafeMemoryPrescreen
|
|
# config:
|
|
# skip_threshold: 0.2 # p below this skips; provisional, unmeasured
|
|
# max_state_chars: 6000 # characters of the judged batch; over it extracts
|
|
# cache_size: 256 # digest-keyed, successful verdicts only
|
|
# cache_ttl_seconds: 300.0
|
|
# # Connection/model/timeouts come from the top-level `typesafe:` block
|
|
# # unless set here (api_key_env / api_key / base_url / model / timeout /
|
|
# # deadline_seconds / max_attempts / retry_backoff).
|
|
#
|
|
# Signal classification (optional, OFF by default): two `noul` questions per
|
|
# batch ("does this explicitly endorse / reject something the user stated
|
|
# earlier?") mapped to the deterministic `reinforcement` / `correction` hints.
|
|
# off (default) - no request.
|
|
# shadow - decide and record only.
|
|
# hints - merge the labels into the extraction hint text and, only while the
|
|
# pre-screen is in `enforce`, veto a skip the model endorsed.
|
|
# A model verdict never decides extraction, never drives deletion, and never
|
|
# satisfies the reinforcement evidence gate (that gate stays deterministic).
|
|
# signal_classification:
|
|
# mode: "off"
|
|
# use: deerflow.agents.memory.signals.typesafe:TypeSafeSignalClassifier
|
|
# combine: auto # auto | always | never: request sharing with the pre-screen
|
|
# config:
|
|
# hint_threshold: 0.5 # provisional, unmeasured
|
|
# max_state_chars: 6000 # a mismatch with prescreen => each side keeps its own request
|
|
# cache_size: 256
|
|
# cache_ttl_seconds: 300.0
|
|
# Backend-private config (a dict), passed verbatim to the backend __init__.
|
|
# Each backend self-interprets it (DeerMem parses it into DeerMemConfig).
|
|
#
|
|
# OpenViking example (replace this DeerMem backend_config block when
|
|
# manager_class is openviking). This first official-adapter integration uses
|
|
# one credential-bound OpenViking USER key for one DeerFlow user and supports
|
|
# middleware mode only. Use owner_user_id: default when DeerFlow auth is off.
|
|
#
|
|
# backend_config:
|
|
# base_url: http://openviking:1933
|
|
# owner_user_id: default
|
|
# api_key_env: OPENVIKING_API_KEY
|
|
# timeout_seconds: 30
|
|
# default_peer_id: deerflow
|
|
# max_seen_message_ids: 512 # bounded hash-only capture cursor
|
|
# startup_policy: fail_fast
|
|
# failure_policy:
|
|
# read: fail_open
|
|
# write: log_and_drop
|
|
# retrieval:
|
|
# top_k: 8
|
|
# score_threshold: 0.25
|
|
# max_injection_chars: 12000
|
|
# content_mode: overview
|
|
# injection_query: >-
|
|
# user profile preferences important entities events ongoing goals
|
|
# constraints and prior decisions
|
|
#
|
|
# For a host-installed OpenViking used by Docker DeerFlow, set base_url to
|
|
# http://host.docker.internal:1933 and allow_insecure_http: true. The bundled
|
|
# optional Compose overlay uses the internal http://openviking:1933 address.
|
|
#
|
|
# Honcho example (replace this DeerMem backend_config block when
|
|
# manager_class is honcho). This remote HTTP adapter uses Honcho's server-side
|
|
# deriver to build user-model memory representations; no local LLM calls.
|
|
#
|
|
# backend_config:
|
|
# base_url: http://localhost:8000
|
|
# # api_key: $HONCHO_API_KEY # hosted Honcho; plain-http + api_key needs allow_insecure_http: true
|
|
# workspace_prefix: deerflow-u- # one isolated workspace per user id
|
|
# # workspace_overrides: {} # map specific user ids to custom workspaces
|
|
# # user_peer_overrides: {} # map specific user ids to custom peer names
|
|
# assistant_peer: deerflow
|
|
backend_config:
|
|
storage_path: "" # empty = deer-flow base_dir (factory injects absolute runtime_home); a non-empty path is the root DIRECTORY (per-user memory under {storage_path}/users/{uid}/memory.json)
|
|
storage_class: file # file (default) or a dotted MemoryStorage class path; invalid persistent backends fail fast
|
|
strict_user_scope: false # set true in authenticated deployments after all callers propagate user_id
|
|
manifest_filename: memory.json # user-global JSON: version/revision/time + user/history only; no facts or fact index
|
|
file_lock_timeout_seconds: 10 # per-scope cross-process advisory lock timeout (single-machine local filesystem)
|
|
retrieval_adapter: fts5 # fts5 (default), empty to disable, or a dotted RetrievalPort factory(config)
|
|
debounce_seconds: 30 # Wait time before processing queued updates
|
|
# Backpressure cap on pending items. 0 = unlimited. At the cap, new
|
|
# non-signal updates are rejected (QueueFull); signal updates are always
|
|
# admitted so important memories are never shed.
|
|
queue_max_depth: 1000
|
|
model: # LLM for memory extraction; omit all fields = no extraction (non-LLM ops still work; an update raises)
|
|
# provider: openai
|
|
# model: gpt-4o-mini
|
|
# api_key: $OPENAI_API_KEY
|
|
# base_url: # optional, for OpenAI-compatible gateways (e.g. DeepSeek)
|
|
# temperature: # optional
|
|
max_facts: 100 # Maximum number of facts to store
|
|
fact_confidence_threshold: 0.7 # Minimum confidence for storing facts
|
|
# Capacity eviction defaults to the historical confidence-only ranking.
|
|
# Opt in to hybrid-v1 to score confidence (65%), explicit-confirmation
|
|
# freshness (25%, 90-day half-life), and query-driven access heat (10%,
|
|
# 30-day half-life). Actual memory_search hits count; default injection does
|
|
# not. A bounded 10% correction reserve aligns storage with guaranteed
|
|
# correction injection. Confirmation detection is batch-level: it checks
|
|
# human messages among the last six filtered messages, while the LLM binds
|
|
# that signal to a factsToReinforce ID without a second correspondence
|
|
# check. Shadow mode audits hybrid disagreements without changing which
|
|
# facts confidence-only keeps.
|
|
fact_eviction_policy: confidence # confidence | hybrid-v1
|
|
fact_eviction_shadow_enabled: false
|
|
eviction_confidence_weight: 0.65
|
|
eviction_confirmation_weight: 0.25
|
|
eviction_access_weight: 0.10
|
|
eviction_confirmation_half_life_days: 90
|
|
eviction_access_half_life_days: 30
|
|
eviction_correction_reserved_fraction: 0.10
|
|
eviction_correction_reserved_max: 10
|
|
eviction_audit_max_entries: 200 # metadata-only events per user/agent scope; 0 disables
|
|
max_injection_tokens: 2000 # Maximum tokens for memory injection
|
|
# Token counting strategy for memory-injection budgeting:
|
|
# tiktoken (default) - accurate, but the encoding BPE data may be
|
|
# downloaded from a public network endpoint on first use. In
|
|
# network-restricted environments this download can block for a long
|
|
# time (see issues #3402 / #3429). Pre-cache the encoding or set this
|
|
# to "char" to avoid it.
|
|
# char - network-free CJK-aware character-based estimate; never touches
|
|
# tiktoken. Slightly less precise budgeting, zero network I/O.
|
|
token_counting: tiktoken
|
|
# Guaranteed injection: fact categories that bypass the regular token budget
|
|
# and draw from a reserved allowance, so high-signal corrections (e.g.
|
|
# "don use pip, use uv") survive even when the budget is tight.
|
|
guaranteed_categories:
|
|
- correction
|
|
guaranteed_token_budget: 500
|
|
# Staleness review: periodically prune aged facts that may no longer reflect
|
|
# the user current situation. When triggered, the LLM reviews facts older
|
|
# than their individual review window (expected_valid_days, or
|
|
# staleness_age_days as fallback) during the normal memory-update call (same
|
|
# LLM invocation - no extra API call) and decides KEEP, REMOVE, or EXTEND
|
|
# for each. The LLM assigns expected_valid_days when creating a fact; EXTEND
|
|
# (staleFactsToExtend) recalibrates that window at review time.
|
|
staleness_review_enabled: true
|
|
staleness_age_days: 90
|
|
staleness_min_candidates: 3
|
|
staleness_max_removals_per_cycle: 10
|
|
staleness_protected_categories:
|
|
- correction
|
|
staleness_max_lifetime_multiplier: 20.0 # creation cap = staleness_age_days x multiplier (90 x 20 = 1800d)
|
|
staleness_max_extension_days: 3650 # absolute ceiling on extended expected_valid_days (~10 years)
|
|
# Memory consolidation (opt-in, lossy: source facts are replaced by a
|
|
# synthesized one; only consolidatedFrom IDs are kept). Runs in the same
|
|
# memory-update LLM call as extraction/staleness - no extra API cost.
|
|
# consolidation_enabled defaults to false because consolidation is lossy.
|
|
consolidation_enabled: false # set true to opt into fact consolidation
|
|
consolidation_min_facts: 8 # min facts in one category to trigger review (3-30)
|
|
consolidation_max_groups_per_cycle: 3 # max groups merged per update cycle (1-10)
|
|
consolidation_max_sources: 8 # max source facts per consolidation group (2-20)
|
|
# extraction_callback is a host-injected post-extraction observability hook
|
|
# (token usage, facts accepted/rejected, rejection rate). The factory
|
|
# injects a logging default; set programmatically to emit a Langfuse span.
|
|
# Message processing (externalized patterns / prompt templates):
|
|
# patterns_dir - dir with correction.yaml / reinforcement.yaml overriding
|
|
# the bundled signal-detection patterns. None (default) =
|
|
# bundled core/message_patterns/. When explicitly set, both
|
|
# files must exist (typos or missing mounts raise an error).
|
|
# prompts_dir - dir with custom memory-extraction prompt templates
|
|
# (memory_update.chat.yaml, staleness_review.yaml,
|
|
# consolidation.yaml, fact_extraction.yaml). None (default)
|
|
# = bundled core/prompts/. Supports per-agent subdirectories.
|
|
# patterns_dir: "" # empty = bundled defaults
|
|
# prompts_dir: "" # empty = bundled defaults
|
|
|
|
# ============================================================================
|
|
# Blob Storage Configuration
|
|
# ============================================================================
|
|
# Content-addressed blob store: content is addressed by its sha256, so writes
|
|
# are idempotent, identical bytes dedupe to one object, and reads verify the
|
|
# digest. Mirrors the memory mechanism: a `backend` name matches a
|
|
# storage/backends/<name>/ folder that exposes STORE_CLASS (e.g. local_fs), or
|
|
# takes a dotted import path to a BlobStore subclass. The factory resolves the
|
|
# backend at get_blob_store() time and fails fast on an unresolvable value --
|
|
# blobs are persistent state, so a broken backend name must not silently swap
|
|
# the storage location.
|
|
#
|
|
# Off by default and purely opt-in: no producer is migrated yet, so a config
|
|
# that never sets this key behaves exactly as before. Contract, the two named
|
|
# producers, and the migration order: backend/docs/blob-storage.md.
|
|
blob_storage:
|
|
enabled: false # master switch (inert until the producer follow-ups land)
|
|
backend: local_fs # folder name under storage/backends/, or a dotted import path
|
|
# Backend-private config (dict), passed verbatim to the backend; each backend
|
|
# self-interprets it. local_fs reads `root` -- empty defaults to
|
|
# {runtime_home}/blobs; point it at a shared volume (NFS / EFS / a
|
|
# ReadWriteMany PVC) for multi-instance deployments:
|
|
#
|
|
# backend_config:
|
|
# root: /mnt/shared/deerflow-blobs
|
|
backend_config: {}
|
|
|
|
# ============================================================================
|
|
# Custom Agent Management API
|
|
# ============================================================================
|
|
# Controls whether the HTTP gateway exposes custom-agent SOUL/USER.md management.
|
|
# Keep this disabled unless the gateway is behind a trusted authenticated admin boundary.
|
|
agents_api:
|
|
enabled: false
|
|
|
|
# ============================================================================
|
|
# Skill Self-Evolution Configuration
|
|
# ============================================================================
|
|
# Allow the agent to autonomously create and improve skills in skills/custom/.
|
|
skill_evolution:
|
|
enabled: false # Set to true to allow agent-managed writes under skills/custom
|
|
moderation_model_name: null # Model for LLM-based security scanning (null = use default model)
|
|
security_fail_closed: true # Moderation model unavailable: true blocks all writes; false allows non-executable content with a warning (executable is always blocked)
|
|
|
|
# ============================================================================
|
|
# Checkpointer Configuration (DEPRECATED — use `database` instead)
|
|
# ============================================================================
|
|
# Legacy standalone checkpointer config. Kept for backward compatibility.
|
|
# Prefer the unified `database` section below, which drives the LangGraph
|
|
# checkpointer, LangGraph Store, and DeerFlow application data (runs,
|
|
# feedback, events) from a single backend setting.
|
|
#
|
|
# If both `checkpointer` and `database` are present, `checkpointer`
|
|
# takes precedence for the LangGraph checkpointer and Store only.
|
|
#
|
|
# checkpointer:
|
|
# type: sqlite
|
|
# # A filesystem path or ':memory:'; SQLite `file:` URIs are rejected.
|
|
# connection_string: checkpoints.db
|
|
#
|
|
# checkpointer:
|
|
# type: postgres
|
|
# connection_string: postgresql://user:password@localhost:5432/deerflow
|
|
# # Optional: place LangGraph checkpointer/store tables in this schema.
|
|
# # Leave empty to use the server default search_path, usually public.
|
|
# postgres_schema: deerflow
|
|
|
|
# ============================================================================
|
|
# Database
|
|
# ============================================================================
|
|
# Unified storage backend for the LangGraph checkpointer, LangGraph Store,
|
|
# and DeerFlow application data (runs, threads metadata, feedback, etc.).
|
|
#
|
|
# backend: memory -- No persistence, data lost on restart
|
|
# backend: sqlite -- Single-node deployment, files in sqlite_dir
|
|
# backend: postgres -- Production multi-node deployment
|
|
#
|
|
# If this section is omitted or empty in config.yaml, DeerFlow uses:
|
|
# backend: sqlite
|
|
# sqlite_dir: .deer-flow/data
|
|
#
|
|
# SQLite mode uses a single deerflow.db file with WAL journal mode
|
|
# for the checkpointer, Store, and application data.
|
|
#
|
|
# Postgres mode: put your connection URL in .env as DATABASE_URL,
|
|
# then reference it here with $DATABASE_URL.
|
|
#
|
|
# Install the driver — Issue #2754 fix lands `UV_EXTRAS` in every code path:
|
|
# Local `make dev` auto-detects from `database.backend: postgres` below
|
|
# and passes `--extra postgres` to `uv sync` on every restart, so
|
|
# the extra is no longer wiped. To opt in explicitly (or layer
|
|
# extras like `postgres,ollama`), set in project-root .env:
|
|
# UV_EXTRAS=postgres
|
|
# Docker dev `make docker-start` reads `UV_EXTRAS` from project-root .env via
|
|
# `env_file`. Set:
|
|
# UV_EXTRAS=postgres
|
|
# Multiple extras (`postgres,ollama`) supported here too — see
|
|
# docker/dev-entrypoint.sh.
|
|
# Docker img build-arg `UV_EXTRAS=postgres,discord docker compose build`
|
|
# supports comma- or whitespace-separated extras at build time
|
|
# (backend/Dockerfile expands them to repeated `--extra` flags).
|
|
#
|
|
# First-time bootstrap (before `make dev`):
|
|
# cd backend && uv sync --all-packages --extra postgres
|
|
# (--all-packages propagates the extra into workspace members — see PR #2584)
|
|
#
|
|
# NOTE: When both `checkpointer` and `database` are configured,
|
|
# `checkpointer` takes precedence for the LangGraph checkpointer and Store;
|
|
# `database` still controls DeerFlow application data.
|
|
# If you use `database`, you can remove the `checkpointer` section.
|
|
# database:
|
|
# backend: sqlite
|
|
# sqlite_dir: .deer-flow/data
|
|
#
|
|
# database:
|
|
# backend: postgres
|
|
# postgres_url: $DATABASE_URL
|
|
# # Optional: place DeerFlow ORM tables and LangGraph checkpointer/store
|
|
# # tables in this schema. Leave empty to use the server default
|
|
# # search_path, usually public.
|
|
# postgres_schema: deerflow
|
|
#
|
|
# postgres_schema only takes effect for new tables. On startup DeerFlow runs
|
|
# CREATE SCHEMA IF NOT EXISTS and pins the connection search_path; it never
|
|
# moves existing tables, so third-party tables in `public` are left untouched.
|
|
#
|
|
# Migrating an EXISTING deployment from `public` to a dedicated schema:
|
|
# 1. Stop the DeerFlow services.
|
|
# 2. Move EVERY DeerFlow-owned table AND the Alembic state into the new schema
|
|
# (run as the DB owner). Missing any table strands its rows: after restart
|
|
# DeerFlow recreates an empty counterpart in the target schema and the old
|
|
# rows stay invisible in `public`. Moving the ORM tables but leaving
|
|
# `alembic_version` behind is especially dangerous -- bootstrap then treats
|
|
# the target schema as unversioned, re-baselines it, and replays migrations.
|
|
# CREATE SCHEMA IF NOT EXISTS deerflow;
|
|
# -- Application ORM tables:
|
|
# ALTER TABLE public.runs SET SCHEMA deerflow;
|
|
# ALTER TABLE public.run_events SET SCHEMA deerflow;
|
|
# ALTER TABLE public.threads_meta SET SCHEMA deerflow;
|
|
# ALTER TABLE public.feedback SET SCHEMA deerflow;
|
|
# ALTER TABLE public.users SET SCHEMA deerflow;
|
|
# ALTER TABLE public.agents SET SCHEMA deerflow;
|
|
# -- IM channel tables:
|
|
# ALTER TABLE public.channel_connections SET SCHEMA deerflow;
|
|
# ALTER TABLE public.channel_credentials SET SCHEMA deerflow;
|
|
# ALTER TABLE public.channel_oauth_states SET SCHEMA deerflow;
|
|
# ALTER TABLE public.channel_conversations SET SCHEMA deerflow;
|
|
# -- Scheduled-task tables:
|
|
# ALTER TABLE public.scheduled_tasks SET SCHEMA deerflow;
|
|
# ALTER TABLE public.scheduled_task_runs SET SCHEMA deerflow;
|
|
# -- Alembic migration state (REQUIRED -- see note above):
|
|
# ALTER TABLE public.alembic_version SET SCHEMA deerflow;
|
|
# The DeerFlow-owned set grows over time; discover any tables the list
|
|
# above misses (as the DeerFlow DB owner/role) with:
|
|
# SELECT table_name FROM information_schema.tables WHERE table_schema='public';
|
|
# LangGraph checkpointer/store table names also vary by version -- list them
|
|
# the same way, then ALTER ... SET SCHEMA deerflow for each (e.g. checkpoints,
|
|
# checkpoint_blobs, checkpoint_writes, checkpoint_migrations, store,
|
|
# store_migrations).
|
|
# 3. Set postgres_schema: deerflow here and restart.
|
|
# 4. Verify with `SHOW search_path;` and a smoke test (create a run, read
|
|
# its history). The `public` schema should gain no new DeerFlow tables.
|
|
database:
|
|
backend: sqlite
|
|
sqlite_dir: .deer-flow/data
|
|
|
|
# ============================================================================
|
|
# Inbound webhook dedupe storage (cross-pod redelivery dedup — issue #4120)
|
|
# ============================================================================
|
|
# Where the ChannelManager records inbound webhook dedupe state.
|
|
#
|
|
# backend: auto -- (default) Postgres whenever database.backend=postgres,
|
|
# including multi-replica K8s where each pod runs a single
|
|
# worker but shares one DB. Otherwise an in-process memory
|
|
# store (single-DB, single-pod).
|
|
# NOTE: multi-replica deployments must use a Postgres DB;
|
|
# if database.backend is sqlite/memory, 'auto' falls back to
|
|
# the per-pod memory store and cross-pod dedupe is disabled.
|
|
# backend: memory -- In-process store only. Per-pod: a webhook redelivered to
|
|
# a DIFFERENT replica is NOT deduped. Not recommended for
|
|
# multi-worker deployments (logs a startup WARNING).
|
|
# backend: postgres -- Share dedupe state across pods via the application DB.
|
|
# REQUIRED for any multi-replica deployment. If
|
|
# database.backend is not 'postgres', a WARNING is logged and
|
|
# the store falls back to the in-process memory store.
|
|
#
|
|
# dedupe_storage:
|
|
# backend: auto
|
|
|
|
# Recycle app ORM PostgreSQL connections before the environment's idle cutoff.
|
|
pool_recycle: 300
|
|
# App ORM PostgreSQL command timeout. Set to null to disable it or raise it
|
|
# for intentionally long commands.
|
|
command_timeout: 30
|
|
# Restart required. Use one value across every process sharing this database.
|
|
# full: current full-message checkpoints; delta: DeltaChannel for messages.
|
|
checkpoint_channel_mode: full
|
|
# Delta-mode tuning (only applies when checkpoint_channel_mode is delta).
|
|
# Restart required, like the mode itself: the cadence is compiled into each
|
|
# graph's channel table. All processes sharing this database must agree.
|
|
# snapshot_frequency: full messages snapshot every N per-step writes
|
|
# (higher = smaller checkpoints, slower materialization).
|
|
checkpoint_delta:
|
|
snapshot_frequency: 10
|
|
# Size caps for the compiled checkpoint graph caches (per process).
|
|
# Hot-reloadable, no restart needed: the value is re-read on each eviction
|
|
# check and only changes when a cache evicts, never graph semantics.
|
|
checkpoint_graph_cache:
|
|
# Gateway thread-state accessor graphs (per assistant/mode/cadence).
|
|
accessor_graph_max: 64
|
|
# Delta-mode checkpoint history cache (only used when checkpoint_channel_mode: delta).
|
|
# Pure performance policy: safe to differ across workers, never frozen.
|
|
# checkpoint_cache:
|
|
# type: memory # memory | redis (redis is Gateway/async only)
|
|
# max_entries: 128 # 0 disables the cache
|
|
# redis_url: null # or DEER_FLOW_CHECKPOINT_CACHE_REDIS_URL / REDIS_URL
|
|
# ttl_seconds: 86400 # redis leak safety net; bounds residual copies if a
|
|
# # thread-delete purge fails (purge itself is immediate).
|
|
# # 0 explicitly disables expiry (redis maxmemory only)
|
|
# key_prefix: "" # default: hash of the database identity
|
|
|
|
# ============================================================================
|
|
# Run Events Configuration
|
|
# ============================================================================
|
|
# Storage backend for run events (messages + execution traces).
|
|
#
|
|
# backend: memory -- No persistence, data lost on restart (default)
|
|
# backend: db -- SQL database via ORM, full query capability (production)
|
|
# backend: jsonl -- Append-only JSONL files (lightweight single-node persistence)
|
|
#
|
|
# run_events:
|
|
# backend: memory
|
|
# max_trace_content: 10240 # Truncation threshold for trace content (db backend, bytes)
|
|
# track_token_usage: true # Accumulate token counts to RunRow
|
|
run_events:
|
|
backend: memory
|
|
max_trace_content: 10240
|
|
track_token_usage: true
|
|
|
|
# ============================================================================
|
|
# Agent Storage Configuration
|
|
# ============================================================================
|
|
# Where custom agent DEFINITIONS (config.yaml + SOUL.md) and deployment-level
|
|
# managed subagent definitions are stored. This is separate from `database`
|
|
# (run/thread/event data) and from agent memory.
|
|
# Restart-required (the backend is captured at Gateway lifespan startup).
|
|
#
|
|
# backend: file -- Custom Agents use per-user files; managed subagents use one
|
|
# JSON file each under {base_dir}/managed-subagents/ (default).
|
|
# Node-local without a shared mount.
|
|
# backend: db -- Both definition types use the shared SQL persistence layer,
|
|
# so every node sees them. Requires database.backend to be
|
|
# 'sqlite' or 'postgres' (rejected at startup on 'memory').
|
|
#
|
|
# Switching an existing install to 'db'? Import the on-disk Custom Agents and
|
|
# managed subagents once with:
|
|
# python backend/scripts/migrate_agents_to_db.py # --dry-run to preview
|
|
# The source files are left in place, so reverting to 'file' is a clean rollback.
|
|
#
|
|
# agent_storage:
|
|
# backend: file
|
|
agent_storage:
|
|
backend: file
|
|
|
|
# ============================================================================
|
|
# Scheduled Tasks Configuration
|
|
# ============================================================================
|
|
# Background scheduler for one-time, cron, and interval agent runs.
|
|
# Poller fields (enabled, multi_instance, poll_interval_seconds, lease_seconds,
|
|
# max_concurrent_runs, max_concurrent_runs_per_user, queue_timeout_seconds,
|
|
# min_once_delay_seconds) are restart-required.
|
|
# recursion_limit is read at dispatch and applies to the next scheduled run
|
|
# without a Gateway restart. The default (1000) matches the web UI's
|
|
# interactive budget so scheduled and interactive runs behave identically out
|
|
# of the box. Values above max_recursion_limit are clamped.
|
|
#
|
|
# Execution slots are shared fairly between task owners. Each owner may hold at
|
|
# most max_concurrent_runs_per_user launching/running scheduled runs (manual
|
|
# "run now" included); the default 2 leaves one of the 3 default global slots
|
|
# for everyone else. The waiting queue is drained owner by owner, so one
|
|
# owner's backlog never pushes another owner's run out of the batch, even with
|
|
# the per-owner cap off (0). A run that waits longer than queue_timeout_seconds
|
|
# is skipped; its run history reads "Skipped: it waited too long for a free
|
|
# slot" and no chat or IM notice is sent, so keep queue_timeout_seconds well
|
|
# above an owner's typical run length.
|
|
#
|
|
# The scheduler is single-instance by default. Set multi_instance: true only
|
|
# when every Gateway instance shares Postgres, run ownership heartbeats, and
|
|
# database-backed run events; startup recovery then preserves live peer runs.
|
|
#
|
|
# When channel_connections.enabled is true and IM channels are running, the
|
|
# same poll_interval_seconds also drives the notification delivery worker that
|
|
# pushes scheduled-task updates to the owner's bound IM identities (only WeCom
|
|
# supports proactive push today; other apps get no notices). Each occurrence
|
|
# sends at most one message: a finished run, a failed run, a missed goal, the
|
|
# automatic pause after three misses, a pause by the agent (stop condition met)
|
|
# or the task finishing (all max_runs done, end_at reached). Notices are queued
|
|
# in the same transaction that records the outcome, so runs finalized by crash
|
|
# or lease recovery notify once. Plain manual "run now" trials and interrupted
|
|
# runs stay silent. The text is in the owner's web UI language (else
|
|
# channel_connections.notification_locale) and has no IDs and no links.
|
|
#
|
|
# scheduler:
|
|
# enabled: false # Master switch for the background poller
|
|
# multi_instance: false # Opt into lease-aware recovery across Gateway instances
|
|
# poll_interval_seconds: 5 # How often to scan for due tasks (+ IM outbox delivery)
|
|
# lease_seconds: 120 # Claim lease; a crashed process's task becomes reclaimable after this
|
|
# max_concurrent_runs: 3 # Global cap on launching/running scheduled runs across multi-instance Pods
|
|
# max_concurrent_runs_per_user: 2 # Per-owner cap (0 = off); never more than max_concurrent_runs
|
|
# queue_timeout_seconds: 3600 # Maximum durable queue wait before an occurrence fails
|
|
# min_once_delay_seconds: 60 # Floor for one-time run_at offset and interval every_seconds
|
|
# recursion_limit: 1000 # LangGraph super-step cap for scheduled runs (matches the web UI)
|
|
scheduler:
|
|
enabled: false
|
|
tool_enabled: false # Offer schedule tools to authorized Gateway conversations
|
|
multi_instance: false # Opt into lease-aware recovery across Gateway instances
|
|
poll_interval_seconds: 5
|
|
lease_seconds: 120
|
|
max_concurrent_runs: 3
|
|
max_concurrent_runs_per_user: 2
|
|
queue_timeout_seconds: 3600
|
|
min_once_delay_seconds: 60
|
|
recursion_limit: 1000
|
|
|
|
# ============================================================================
|
|
# Long-running MCP Tasks Configuration
|
|
# ============================================================================
|
|
# Durable runtime for ordinary MCP submit/status/cancel task toolsets. It is
|
|
# disabled by default; task_toolsets are configured per server in
|
|
# extensions_config.json.
|
|
# All fields are restart-required (captured at Gateway lifespan startup).
|
|
#
|
|
# mcp_tasks:
|
|
# enabled: false # Master switch for the background status poller
|
|
# poll_interval_seconds: 5 # Scan interval and default task retry interval
|
|
# lease_seconds: 120 # Expired claims become recoverable after this delay
|
|
# max_concurrent_polls: 8 # Maximum status calls started by one worker per scan
|
|
# max_poll_backoff_seconds: 300 # Cap for exponential retries after transient errors
|
|
# input_required_poll_interval_seconds: 60 # Minimum poll interval while waiting for user input
|
|
# tracking_degraded_after_errors: 3 # Consecutive errors before query API reports degraded tracking
|
|
# max_result_bytes: 65536 # Full JSON result storage limit
|
|
# result_preview_max_chars: 2000 # Text preview retained when a result exceeds the limit
|
|
mcp_tasks:
|
|
enabled: false
|
|
poll_interval_seconds: 5
|
|
lease_seconds: 120
|
|
max_concurrent_polls: 8
|
|
max_poll_backoff_seconds: 300
|
|
input_required_poll_interval_seconds: 60
|
|
tracking_degraded_after_errors: 3
|
|
max_result_bytes: 65536
|
|
result_preview_max_chars: 2000
|
|
|
|
# ============================================================================
|
|
# Deployment Topology
|
|
# ============================================================================
|
|
# Declare whether more than one Gateway instance shares this database.
|
|
#
|
|
# GATEWAY_WORKERS / WEB_CONCURRENCY only count the uvicorn workers of one
|
|
# process tree, so a Kubernetes Deployment with replicas > 1 (one worker per
|
|
# Pod) is invisible to the startup safety gate. Without this declaration each
|
|
# Pod passes every check, and its startup orphan reconciliation writes the
|
|
# other Pods' lease-less runs off as crashed on every rolling update.
|
|
#
|
|
# With multi_instance: true (or the environment variable
|
|
# DEER_FLOW_MULTI_INSTANCE=1, which deploy tooling such as a Helm chart can
|
|
# set from its replica count) startup requires database.backend: postgres, run_events.backend: db,
|
|
# run_ownership.heartbeat_enabled: true and a Redis stream bridge, and refuses
|
|
# an explicit sandbox.ownership.type: memory, process-local browser tools, and
|
|
# scheduler.enabled: true without scheduler.multi_instance: true.
|
|
# Restart required; apply the same value to every instance.
|
|
deployment:
|
|
multi_instance: false
|
|
|
|
# ============================================================================
|
|
# Run Ownership Configuration
|
|
# ============================================================================
|
|
# Controls cross-process run ownership for multi-worker deployments.
|
|
# When GATEWAY_WORKERS > 1, each worker claims runs with a lease; the heartbeat
|
|
# renews leases, and reconciliation recovers orphaned runs from crashed workers.
|
|
#
|
|
# CLOCK-SYNC REQUIREMENT (multi-worker only): reconciliation compares another
|
|
# worker's UTC lease timestamp against this worker's datetime.now(UTC). Worker
|
|
# clocks MUST be synced (NTP / chrony / systemd-timesyncd — default on K8s and
|
|
# cloud VMs) within a few seconds. grace_seconds is the skew budget; worst case
|
|
# (owning worker's heartbeat just about to fire), a peer whose clock is more
|
|
# than grace_seconds ahead can mis-reclaim a still-live run as an orphan. Raise
|
|
# grace_seconds if your environment cannot keep clocks within a few seconds;
|
|
# the trade-off is longer recovery latency for genuinely dead workers
|
|
# (lease_seconds + grace_seconds from last heartbeat to reclaim).
|
|
#
|
|
# The owning worker itself does NOT use grace_seconds as extra execution time.
|
|
# If renewal cannot be confirmed before lease_seconds expires, it cancels the
|
|
# local run and suppresses durable finalization; grace_seconds only delays when
|
|
# a peer may reclaim the expired row.
|
|
|
|
run_ownership:
|
|
lease_seconds: 30 # Seconds before a run lease expires if not renewed.
|
|
# Heartbeat renews every lease_seconds / 3.
|
|
grace_seconds: 10 # Extra seconds past expiry before reclaiming an orphaned run.
|
|
# Also the cross-worker clock-skew budget — see note above.
|
|
heartbeat_enabled: false # Set to true for GATEWAY_WORKERS > 1
|
|
|
|
# ============================================================================
|
|
# Stream Bridge Configuration
|
|
# ============================================================================
|
|
# The stream bridge carries live agent events from gateway workers to SSE
|
|
# clients. Docker Compose sets DEER_FLOW_STREAM_BRIDGE_REDIS_URL automatically,
|
|
# so Docker deployments use Redis Streams even if this section is omitted.
|
|
#
|
|
# The redis bridge requires the optional `redis` extra. It is auto-detected from
|
|
# this section on `make dev`, and always installed in the Docker image. To install
|
|
# it manually: cd backend && uv sync --all-packages --extra redis
|
|
#
|
|
# stream_bridge:
|
|
# type: memory # single-process only
|
|
# queue_maxsize: 256 # data events retained per run (min: 1). A reconnect
|
|
# # older than this window receives SSE `gap`.
|
|
# heartbeat_interval_seconds: 15 # idle interval (max: 86400) between
|
|
# # heartbeats sent to SSE, wait, and internal
|
|
# # stream consumers.
|
|
#
|
|
# stream_bridge:
|
|
# type: redis # recommended for Docker / multi-worker gateway
|
|
# redis_url: redis://redis:6379/0
|
|
# queue_maxsize: 256 # data events retained per run (Redis MAXLEN, min: 1).
|
|
# # Older valid cursors receive SSE `gap`.
|
|
# heartbeat_interval_seconds: 15 # idle interval (max: 86400) between
|
|
# # heartbeats sent to SSE, wait, and internal
|
|
# # stream consumers.
|
|
# stream_ttl_seconds: 86400 # rolling TTL for retained stream buffers.
|
|
# # Refreshed on each publish/publish_end; set 0
|
|
# # to disable. This is not a run timeout.
|
|
# recovered_stream_cleanup_delay_seconds: 60 # seconds to wait after
|
|
# # publishing END for a recovered orphan run
|
|
# # before deleting the stream key (max: 86400).
|
|
# max_connections: 100 # optional pool ceiling. Each live SSE client
|
|
# # holds one connection blocked in XREAD ... BLOCK
|
|
# # for up to the configured heartbeat interval, so
|
|
# # hundreds of concurrent clients open hundreds of
|
|
# # connections. Unset = redis-py default (unbounded).
|
|
#
|
|
# NOTE: the redis bridge is fail-hard in v1. Redis.from_url is lazy, so a down
|
|
# Redis does not block gateway startup, but the first publish/xread raises. A
|
|
# mid-run Redis outage fails the active run — there is no automatic retry/backoff
|
|
# or fallback to the in-memory bridge. Run Redis with HA / a restart policy.
|
|
|
|
# ============================================================================
|
|
# User-Owned IM Channel Connections
|
|
# ============================================================================
|
|
# Lets logged-in users connect their own IM accounts from the DeerFlow frontend
|
|
# while reusing the existing `channels` runtime configuration below.
|
|
#
|
|
# Security notes:
|
|
# - No public IP, OAuth callback URL, or provider webhook is required.
|
|
# - Provider bot/app credentials stay under `channels.*`.
|
|
# - `channel_connections` stores per-user bindings and one-time connect codes.
|
|
# - Telegram uses a deep link when `bot_username` is configured.
|
|
# - Slack, Discord, Feishu, DingTalk, WeChat, WeCom, and QQ use `/connect <code>`
|
|
# through the already-running bot/app.
|
|
# - When enabled (and channels are running), scheduled-task updates (runs,
|
|
# pauses, finishes) are pushed to the owner's connected identities on apps
|
|
# with proactive push, WeCom today (see scheduler section above). Settings
|
|
# shows per app whether updates are sent there. Plain manual trials and
|
|
# interrupted runs do not notify. Notices carry no links.
|
|
#
|
|
# channel_connections:
|
|
# enabled: false
|
|
# # Security: keep this enabled unless you intentionally want legacy open-bot behavior.
|
|
# # Disabling it lets unbound external IM users create DeerFlow threads/runs.
|
|
# require_bound_identity: true
|
|
# # Language of scheduled-task IM notices (en-US or zh-CN) for owners without
|
|
# # a web UI language preference (e.g. auth disabled). Restart-required.
|
|
# notification_locale: en-US
|
|
#
|
|
# telegram:
|
|
# enabled: false
|
|
# bot_username: $TELEGRAM_BOT_USERNAME
|
|
#
|
|
# slack:
|
|
# enabled: false
|
|
#
|
|
# discord:
|
|
# enabled: false
|
|
#
|
|
# feishu:
|
|
# enabled: false
|
|
#
|
|
# dingtalk:
|
|
# enabled: false
|
|
#
|
|
# wechat:
|
|
# enabled: false
|
|
#
|
|
# wecom:
|
|
# enabled: false
|
|
#
|
|
# buzz:
|
|
# enabled: false
|
|
#
|
|
# qq:
|
|
# enabled: false
|
|
|
|
# ============================================================================
|
|
# IM Channels Configuration
|
|
# ============================================================================
|
|
# Connect DeerFlow to external messaging platforms.
|
|
# All channels use outbound connections (WebSocket or polling) — no public IP required.
|
|
|
|
# channels:
|
|
# # LangGraph-compatible Gateway API base URL for thread/message management (default: http://localhost:8001/api)
|
|
# # For Docker deployments, use the Docker service name instead of localhost:
|
|
# # langgraph_url: http://gateway:8001/api
|
|
# # gateway_url: http://gateway:8001
|
|
# langgraph_url: http://localhost:8001/api
|
|
# # Gateway API URL for auxiliary queries like /models, /memory (default: http://localhost:8001)
|
|
# gateway_url: http://localhost:8001
|
|
# # Maximum queued or provider-reserved inbound messages. Must be a positive integer.
|
|
# inbound_queue_maxsize: 1000
|
|
# # Fixed number of long-lived inbound handler workers. Must be a positive integer.
|
|
# max_concurrency: 5
|
|
# # Seconds to drain accepted inbound work before cancelling active handlers.
|
|
# # Must be a non-negative finite number. Cancelled handlers are awaited; the Gateway's
|
|
# # outer shutdown timeout remains the process-level bound for incomplete cleanup.
|
|
# shutdown_grace_period_seconds: 3
|
|
# #
|
|
# # Docker Compose note:
|
|
# # If channels run inside the gateway container, use container DNS names instead
|
|
# # of localhost, for example:
|
|
# # langgraph_url: http://gateway:8001/api
|
|
# # gateway_url: http://gateway:8001
|
|
# # You can also set DEER_FLOW_CHANNELS_LANGGRAPH_URL / DEER_FLOW_CHANNELS_GATEWAY_URL.
|
|
#
|
|
# # Optional: default mobile/session settings for all IM channels
|
|
# session:
|
|
# assistant_id: lead_agent # or a custom agent name; custom agents route via lead_agent + agent_name
|
|
# config:
|
|
# recursion_limit: 100
|
|
# context:
|
|
# thinking_enabled: true
|
|
# is_plan_mode: false
|
|
# subagent_enabled: false
|
|
#
|
|
# # QQ Open Platform bot; choose WebSocket in the developer console.
|
|
# # Text-only C2C and group @mentions; no public callback URL required.
|
|
# qq:
|
|
# enabled: false
|
|
# app_id: $QQ_APP_ID
|
|
# client_secret: $QQ_CLIENT_SECRET
|
|
# allowed_users: [] # QQ OpenIDs, not numeric QQ account numbers; empty allows all
|
|
# sandbox: false # REST sandbox host only; console sandbox membership is separate
|
|
#
|
|
# feishu:
|
|
# enabled: false
|
|
# app_id: $FEISHU_APP_ID
|
|
# app_secret: $FEISHU_APP_SECRET
|
|
# # domain: https://open.feishu.cn # China (default)
|
|
# # domain: https://open.larksuite.com # International
|
|
#
|
|
# slack:
|
|
# enabled: false
|
|
# bot_token: $SLACK_BOT_TOKEN # xoxb-...
|
|
# app_token: $SLACK_APP_TOKEN # xapp-... (Socket Mode)
|
|
# allowed_users: [] # empty = allow all; can also be a single Slack user ID string, e.g. U123456, but list form is recommended
|
|
#
|
|
# telegram:
|
|
# enabled: false
|
|
# bot_token: $TELEGRAM_BOT_TOKEN
|
|
# allowed_users: [] # numeric user IDs, not @usernames; empty = allow all
|
|
# rich_messages: false # Bot API 10.1 Rich Messages for final Markdown responses
|
|
#
|
|
# wechat:
|
|
# enabled: false
|
|
# bot_token: $WECHAT_BOT_TOKEN
|
|
# ilink_bot_id: $WECHAT_ILINK_BOT_ID
|
|
# # Optional: allow first-time QR bootstrap when bot_token is absent
|
|
# qrcode_login_enabled: true
|
|
# # Optional: sent as iLink-App-Id header when provided
|
|
# ilink_app_id: ""
|
|
# # Optional: sent as SKRouteTag header when provided
|
|
# route_tag: ""
|
|
# allowed_users: [] # iLink user IDs; empty allows all; a single ID is one entry; unparseable non-empty lists fail closed
|
|
# # Optional: extra host suffixes inbound media downloads may come from,
|
|
# # in addition to the platform CDN defaults (*.qq.com and the cdn_base_url host).
|
|
# # Entries are suffixes: "example.com" and "*.example.com" are equivalent.
|
|
# allowed_media_hosts: []
|
|
# # Optional: timing values must be positive finite seconds
|
|
# polling_timeout: 35
|
|
# polling_retry_delay: 5
|
|
# # QR poll interval when qrcode_login_enabled is true
|
|
# qrcode_poll_interval: 2
|
|
# # QR bootstrap timeout
|
|
# qrcode_poll_timeout: 180
|
|
# # Optional: persist getupdates cursor under the gateway container volume
|
|
# state_dir: ./.deer-flow/wechat/state
|
|
# # Optional: max inbound image size in bytes before skipping download
|
|
# max_inbound_image_bytes: 20971520
|
|
# # Optional: max outbound image size in bytes before skipping upload
|
|
# max_outbound_image_bytes: 20971520
|
|
# # Optional: max inbound file size in bytes before skipping download
|
|
# max_inbound_file_bytes: 52428800
|
|
# # Optional: max outbound file size in bytes before skipping upload
|
|
# max_outbound_file_bytes: 52428800
|
|
# # Optional: allowed file extensions for regular file receive/send
|
|
# allowed_file_extensions: [".txt", ".md", ".pdf", ".csv", ".json", ".yaml", ".yml", ".xml", ".html", ".log", ".zip", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".rtf"]
|
|
#
|
|
# # Optional: channel-level session overrides
|
|
# session:
|
|
# assistant_id: mobile-agent # custom agent names are supported here too
|
|
# context:
|
|
# thinking_enabled: false
|
|
#
|
|
# # Optional: per-user overrides by user_id
|
|
# users:
|
|
# "123456789":
|
|
# assistant_id: vip-agent
|
|
# config:
|
|
# recursion_limit: 150
|
|
# context:
|
|
# thinking_enabled: true
|
|
# subagent_enabled: true
|
|
# wecom:
|
|
# enabled: false
|
|
# bot_id: $WECOM_BOT_ID
|
|
# bot_secret: $WECOM_BOT_SECRET
|
|
# # Optional: extra host suffixes inbound media downloads may come from, in
|
|
# # addition to the built-in qq.com family and WeCom's official COS media
|
|
# # host (ww-aibot-img-1258476243.<region>.myqcloud.com). Add a suffix here
|
|
# # if WeCom rotates to a new COS account or media is routed through a proxy.
|
|
# allowed_media_hosts: []
|
|
#
|
|
# dingtalk:
|
|
# enabled: false
|
|
# client_id: $DINGTALK_CLIENT_ID
|
|
# client_secret: $DINGTALK_CLIENT_SECRET
|
|
# allowed_users: [] # empty = allow all
|
|
# card_template_id: "" # Optional: AI Card template ID for streaming updates
|
|
#
|
|
# # Discord joins as a bot; requires the `discord` dependency extra
|
|
# # (uv sync --extra discord) for discord.py.
|
|
# discord:
|
|
# enabled: false
|
|
# bot_token: $DISCORD_BOT_TOKEN
|
|
# # Allowed guild IDs: one positive numeric ID (quoted or unquoted) or a YAML list.
|
|
# # Unset, null, [], or a blank string allows all guilds.
|
|
# # Invalid entries are ignored with a warning. Any other configured value
|
|
# # with no valid ID denies every guild and logs an error.
|
|
# allowed_guilds: []
|
|
# mention_only: false # If true, only respond when the bot is mentioned
|
|
# # Channel IDs exempt from mention_only: one ID (quoted or unquoted) or a YAML list.
|
|
# # Empty = no exemptions; mention_only applies to every channel when enabled.
|
|
# allowed_channels: []
|
|
# thread_mode: false # If true, group a channel conversation into a thread
|
|
#
|
|
# # Buzz (https://github.com/block/buzz) — Nostr-relay team workspace.
|
|
# # DeerFlow joins as a member identity; generate a keypair and register it as
|
|
# # a relay member, then @mention or DM DeerFlow in Buzz. Requires the `buzz`
|
|
# # dependency extra (uv sync --extra buzz).
|
|
# buzz:
|
|
# enabled: false
|
|
# relay_url: wss://buzz.example.com
|
|
# private_key: $BUZZ_PRIVATE_KEY # hex or nsec1…
|
|
# allowed_users: [] # pubkeys (hex or npub) allowed to trigger runs.
|
|
# # DENY-BY-DEFAULT (unlike other channels): empty
|
|
# # means nobody, and DeerFlow logs a startup warning.
|
|
# require_mention: true # @mention needed in channels
|
|
# mention_free_channels: [] # channel UUIDs that respond to every message
|
|
|
|
# ============================================================================
|
|
# Guardrails Configuration
|
|
# ============================================================================
|
|
# Optional pre-execution authorization for tool calls.
|
|
# When enabled, every tool call passes through the configured provider
|
|
# before execution. Four options: built-in allowlist, OAP policy provider,
|
|
# custom provider, or the TypeSafe (Jev) risk gate. See
|
|
# backend/docs/GUARDRAILS.md for full documentation.
|
|
#
|
|
# Providers are loaded by class path via resolve_variable (same as models/tools).
|
|
|
|
# --- Option 1: Built-in AllowlistProvider (zero external deps) ---
|
|
# guardrails:
|
|
# enabled: true
|
|
# provider:
|
|
# use: deerflow.guardrails.builtin:AllowlistProvider
|
|
# config:
|
|
# denied_tools: ["bash", "write_file"]
|
|
|
|
# --- Option 2: OAP passport provider (open standard, any implementation) ---
|
|
# The Open Agent Passport (OAP) spec defines passport format and decision codes.
|
|
# Any OAP-compliant provider works. Example using APort (reference implementation):
|
|
# pip install aport-agent-guardrails && aport setup --framework deerflow
|
|
# guardrails:
|
|
# enabled: true
|
|
# provider:
|
|
# use: aport_guardrails.providers.generic:OAPGuardrailProvider
|
|
|
|
# --- Option 3: Custom provider (any class with evaluate/aevaluate methods) ---
|
|
# guardrails:
|
|
# enabled: true
|
|
# provider:
|
|
# use: my_package:MyGuardrailProvider
|
|
# config:
|
|
# key: value
|
|
|
|
# --- Option 4: TypeSafe (Jev) pre-execution risk gate (network provider) ---
|
|
# Sends the tool name and its full argument JSON to POST /v1/systemone and
|
|
# denies calls whose risk probability reaches `threshold`. Requires an API
|
|
# key: export TYPESAFE_API_KEY=... (or set `api_key` below).
|
|
#
|
|
# `tools: ["bash"]` is a SMOKE-TEST scope, not a production recommendation:
|
|
# the operation can move to an unprobed tool, an MCP tool or a subagent, and
|
|
# long heredocs / large write_file payloads are denied locally once the
|
|
# argument text exceeds max_state_chars. Probe every tool that can perform the
|
|
# risky work instead of narrowing the gate. Pointing `guardrails.provider` at
|
|
# TypeSafe REPLACES the allowlist/OAP provider: the allow half of an old
|
|
# allowlist moves into `allowed_tools` below, while deny rules still belong to
|
|
# `authorization.*` (see backend/docs/GUARDRAILS.md, Option 4).
|
|
# guardrails:
|
|
# enabled: true
|
|
# fail_closed: true
|
|
# provider:
|
|
# use: deerflow.guardrails.typesafe:TypeSafeGuardrailProvider
|
|
# config:
|
|
# model: jev-latest # pin an exact version (e.g. jev-1.13.0) when
|
|
# # decisions must stay traceable across releases
|
|
# threshold: 0.5 # example value; calibrate with the online evaluation
|
|
# tools: ["bash"]
|
|
# # allowed_tools: ["bash", "read_file", "write_file"] # hard permission list:
|
|
# # unlisted tools are refused locally before any state is built, and
|
|
# # listed tools still have to pass the risk gate. Omit to enforce none;
|
|
# # [] refuses every tool.
|
|
# # Optional: api_key / api_key_env (default TYPESAFE_API_KEY) / base_url /
|
|
# # timeout / deadline_seconds / max_attempts / retry_backoff /
|
|
# # max_state_chars / cache_size / cache_ttl_seconds /
|
|
# # instructions / criteria
|
|
|
|
# ============================================================================
|
|
# TypeSafe (Jev) Client Defaults (optional)
|
|
# ============================================================================
|
|
# One block for every TypeSafe consumer: the guardrail risk gate
|
|
# (guardrails.provider.config) and, once enabled, memory pre-screening and
|
|
# signal classification. Each consumer overrides what it needs in its own
|
|
# `config`; precedence is consumer config > this block > built-in defaults.
|
|
#
|
|
# Not here, on purpose: failure policy (the gate denies on error, the memory
|
|
# paths carry on), cache settings, thresholds and question content. Those are
|
|
# the consumer's own; two consumers with different thresholds can still share
|
|
# one request, and each publishes its own policy identity.
|
|
#
|
|
# Credentials: prefer api_key_env. `api_key` here puts the key in this file.
|
|
# A consumer's `config` and this block are separate layers: the first layer that
|
|
# sets `api_key` or `api_key_env` decides the credential, so a consumer with its
|
|
# own `api_key_env` keeps reading that variable. Inside one layer a literal
|
|
# `api_key` wins over that layer's `api_key_env`.
|
|
#
|
|
# These values are checked when a consumer is built, not on the first call: a
|
|
# `base_url` that is not http(s) or that carries a query / fragment / embedded
|
|
# credentials, a key with surrounding whitespace (a secret mounted from a file),
|
|
# and a non-printable key character are all configuration errors. Numbers here are
|
|
# validated strictly, so write `max_attempts: 2` — `true` is rejected rather than
|
|
# silently read as `1`.
|
|
#
|
|
# typesafe:
|
|
# api_key_env: TYPESAFE_API_KEY # default; the variable holding the key
|
|
# # api_key: <explicit key> # alternative to api_key_env
|
|
# base_url: https://api.typesafe.ai # default; http(s) only, no query/fragment/credentials
|
|
# model: jev-latest # default; pin an exact version for traceability
|
|
# timeout: 5.0 # per-request timeout (seconds)
|
|
# deadline_seconds: 10.0 # whole-evaluation budget (seconds)
|
|
# max_attempts: 2 # includes the first; 429/529 and transport errors only
|
|
# retry_backoff: 0.5 # base seconds of exponential backoff
|
|
|
|
# ============================================================================
|
|
# Authorization Configuration
|
|
# ============================================================================
|
|
# Fine-grained resource authorization (RBAC and beyond). Disabled by default;
|
|
# every authenticated user has access to all resources.
|
|
# See RFC: https://github.com/bytedance/deer-flow/issues/4063
|
|
#
|
|
# authorization:
|
|
# enabled: true
|
|
# fail_closed: true # block on provider error / unresolved identity
|
|
# default_role: user # applied when user_role is None; built-in RBAC requires this role below
|
|
# provider:
|
|
# use: deerflow.authz.rbac:RbacAuthorizationProvider
|
|
# config:
|
|
# # A known role with no policy for a resource is unrestricted for it.
|
|
# # Define both `tools` and `routes` wherever access should be constrained.
|
|
# # `models` / `skills` govern the per-caller listing/detail surfaces
|
|
# # (which models/skills the caller sees on GET /api/models and the
|
|
# # GET /api/skills[.../custom, /{name}] skill routes); skill
|
|
# # management endpoints (install/edit/export) stay admin-gated.
|
|
# #
|
|
# # Plugin resources: `plugin_actions` gates invoking a registered
|
|
# # backend action (POST /api/plugins/{ns}/actions/{name}), and
|
|
# # `plugin_management` gates an enterprise-contributed management route
|
|
# # that calls require_plugin_management. Targets are
|
|
# # "<namespace>/<action-name>" and "<namespace>/permissions.read"
|
|
# # / "<namespace>/permissions.write". **Omitting a key leaves that
|
|
# # resource unrestricted for the role**, so list it explicitly to
|
|
# # constrain it. Declared plugin pages are not gated yet: their
|
|
# # `plugin_pages` row ships with the page-authorization slice.
|
|
# roles:
|
|
# admin:
|
|
# tools: {allow: "*"}
|
|
# routes: {allow: "*"}
|
|
# models: {allow: "*"}
|
|
# sandbox: {allow: "*"}
|
|
# skills: {allow: "*"}
|
|
# plugin_actions: {allow: "*"}
|
|
# plugin_management: {allow: "*"}
|
|
# user:
|
|
# tools: {allow: "*", deny: ["update_agent"]}
|
|
# routes: {allow: "*"}
|
|
# models: {allow: "*"}
|
|
# sandbox: {allow: "*"}
|
|
# skills: {allow: "*"}
|
|
# plugin_actions: {allow: "*"} # invoke any installed action
|
|
# plugin_management: {allow: []} # no management route access
|
|
# guest:
|
|
# # web-only role: sandbox-dependent tools (read_file, bash, glob,
|
|
# # grep, write_file, ...) are omitted — with sandbox:execute denied
|
|
# # they could only ever return the deny error, so allow just the
|
|
# # non-sandbox tools.
|
|
# tools: {allow: ["web_search"]}
|
|
# routes: {allow: ["threads:read", "runs:read"]}
|
|
# models: {allow: ["gpt-4o-mini"]}
|
|
# sandbox: {allow: false} # deny sandbox execution
|
|
# skills: {allow: []} # no skills visible in the listing
|
|
# plugin_actions: {allow: ["acme.reports/export"]}
|
|
# plugin_management: {allow: ["acme.reports/permissions.read"]}
|
|
authorization:
|
|
enabled: false
|
|
|
|
# ============================================================================
|
|
# Circuit Breaker Configuration
|
|
# ============================================================================
|
|
# Circuit breaker for LLM calls prevents repeated requests to a failing provider.
|
|
# When the failure threshold is reached, subsequent calls fast-fail until recovery.
|
|
#
|
|
# This is useful for:
|
|
# - Avoiding rate-limit bans during provider outages
|
|
# - Reducing resource exhaustion from retry loops
|
|
# - Gracefully degrading when LLM services are unavailable
|
|
|
|
# circuit_breaker:
|
|
# # Number of consecutive failures before opening the circuit (default: 5)
|
|
# failure_threshold: 5
|
|
# # Time in seconds before attempting to recover (default: 60)
|
|
# recovery_timeout_sec: 60
|
|
|
|
# ============================================================================
|
|
# LLM Call Concurrency Configuration
|
|
# ============================================================================
|
|
# Cap the number of concurrently in-flight LLM calls process-wide. A provider
|
|
# burst-rate (limit_burst_rate) error fires on the *slope* of the request rate,
|
|
# not on a static quota - so the morning peak (e.g. 08:30) ramping from ~0 to
|
|
# full throttle in seconds gets rejected even when total RPM is within budget.
|
|
# Capping concurrency caps that slope. Retries alone make it worse (they add
|
|
# demand inside the very burst being throttled); pair this cap with the
|
|
# decorrelated-jitter backoff already built into the LLM error-handling
|
|
# middleware, and ideally an nginx `limit_req` at the ingress.
|
|
#
|
|
# 0 disables the cap (default) - existing deployments see no behavior change.
|
|
#
|
|
# Per-process, not per-cluster: the cap bounds in-flight LLM calls within ONE
|
|
# gateway process. With GATEWAY_WORKERS > 1 the aggregate cap across the
|
|
# deployment is effectively `max_concurrent_calls * GATEWAY_WORKERS`, and a
|
|
# multi-node rollout multiplies it further - size the per-process value with
|
|
# that in mind (and pair it with an nginx `limit_req` at the ingress for a
|
|
# true cluster-wide slope cap).
|
|
#
|
|
# Startup-only: the cap is captured at the first LLM run and frozen for the
|
|
# process lifetime. Editing `max_concurrent_calls` here takes effect only after
|
|
# a gateway restart; the other `llm_call.*` knobs below remain hot-reloadable.
|
|
# Freezing the cap avoids the downscale and config-freshness races that a
|
|
# runtime-mutable, process-wide/cross-loop limiter would otherwise hit (a
|
|
# lowered cap could keep admitting queued waiters, or a stale config snapshot
|
|
# constructed after a fresher one could restore a higher cap).
|
|
|
|
# llm_call:
|
|
# # Max concurrently in-flight LLM calls across the whole process (default: 0 = disabled)
|
|
# max_concurrent_calls: 0
|
|
# # Max LLM call attempts for retriable transient errors, 1 = no retry (default: 3)
|
|
# retry_max_attempts: 3
|
|
# # Base delay (ms) for the decorrelated-jitter retry backoff (default: 1000)
|
|
# retry_base_delay_ms: 1000
|
|
# # Hard cap (ms) on any single retry backoff delay (default: 8000)
|
|
# retry_cap_delay_ms: 8000
|
|
# # Backoff base (ms) used ONLY for burst-rate (limit_burst_rate) 429s - higher
|
|
# # than retry_base_delay_ms so the single burst retry lands after the throttle
|
|
# # window subsides. Ignored when the provider sends Retry-After (default: 5000)
|
|
# burst_retry_base_delay_ms: 5000
|
|
|
|
# ============================================================================
|
|
# SSO / OIDC Authentication (optional)
|
|
# ============================================================================
|
|
# Enable SSO login via any OIDC-compatible provider (Keycloak, Google, Azure AD, Okta, etc.).
|
|
# When enabled, the login page will show SSO buttons alongside the standard email/password form.
|
|
#
|
|
# Provider configuration:
|
|
# - issuer: The OIDC issuer URL (e.g. https://keycloak.example.com/realms/deerflow)
|
|
# - client_id: OAuth2 client ID from the provider
|
|
# - client_secret: OAuth2 client secret ($ENV_VAR references supported)
|
|
# - redirect_uri: Callback URL. In production, this must match what you configure in
|
|
# the provider. Defaults to a self-derived URL in development.
|
|
#
|
|
# Keycloak setup:
|
|
# 1. Create a client with type "confidential" and Standard Flow enabled
|
|
# 2. Add Valid Redirect URI: http://localhost:8001/api/v1/auth/callback/keycloak
|
|
# 3. Add Web Origin: http://localhost:8001 (or your frontend origin)
|
|
|
|
# ============================================================================
|
|
# Local (email/password) authentication
|
|
# ============================================================================
|
|
# Self-registration via POST /api/v1/auth/register is open by default: anyone
|
|
# who can reach the Gateway may create a regular user account.
|
|
#
|
|
# The OIDC provisioning policy below (allowed_email_domains, require_verified_email,
|
|
# auto_create_users) is enforced only in the SSO callback. It does NOT constrain local
|
|
# registration, so on an SSO-provisioned deployment leaving this open lets a visitor
|
|
# create an account outside that policy. Set allow_registration: false there.
|
|
#
|
|
# The first admin account is always created through /initialize regardless of this
|
|
# setting, so turning it off cannot lock you out of a fresh install.
|
|
|
|
# auth:
|
|
# local:
|
|
# allow_registration: false
|
|
# # Login throttling per client IP (in-process, per worker). Raise
|
|
# # max_login_attempts when many users share one egress IP (corporate
|
|
# # proxy / NAT) so shared-IP lockouts don't block the whole office;
|
|
# # lower it for a stricter posture (minimum 2 — one failed attempt must
|
|
# # never lock an IP, or a single typo would block the shared egress).
|
|
# # Defaults preserve the historical hardcoded policy (5 failures /
|
|
# # 5 minutes). Live-read per login, so a config reload applies without
|
|
# # a Gateway restart: lowering lockout_seconds releases an active lock
|
|
# # early and tightening max_login_attempts keeps already-counted
|
|
# # failures; a raised lockout_seconds extends a still-active lock but
|
|
# # never resurrects one that already expired.
|
|
# # max_login_attempts: 5
|
|
# # lockout_seconds: 300
|
|
#
|
|
# oidc:
|
|
# enabled: true
|
|
# # Base URL of the frontend, used for redirects after SSO callback.
|
|
# # In production behind a reverse proxy, set this to the public frontend URL
|
|
# # (e.g. https://deerflow.example.com). In development, leave unset.
|
|
# # frontend_base_url: http://localhost:3000
|
|
# providers:
|
|
# keycloak:
|
|
# display_name: Keycloak
|
|
# issuer: https://keycloak.example.com/realms/deerflow
|
|
# client_id: deerflow
|
|
# client_secret: $KEYCLOAK_CLIENT_SECRET
|
|
# # Optional: explicitly set the callback URL.
|
|
# # redirect_uri: https://deerflow.example.com/api/v1/auth/callback/keycloak
|
|
# scopes:
|
|
# - openid
|
|
# - email
|
|
# - profile
|
|
# token_endpoint_auth_method: client_secret_post
|
|
#
|
|
# # User provisioning settings (safe defaults shown below):
|
|
# auto_create_users: true # Auto-create DeerFlow account on first SSO login
|
|
# require_verified_email: true # Reject SSO logins without verified email
|
|
# # allowed_email_domains: # Restrict to specific email domains
|
|
# # - example.com
|
|
# admin_emails: [] # Auto-grant admin role to these emails
|
|
#
|
|
# # Security features (enabled by default):
|
|
# pkce_enabled: true # PKCE (S256) for authorization code flow
|
|
# nonce_enabled: true # Nonce validation in ID tokens
|
|
|
|
# --- Plugins ----------------------------------------------------------------
|
|
# Extension packages, loaded once at startup in the order listed here. Each
|
|
# entry names an install entry point as "module.path:install"; the `config`
|
|
# block is handed to that package verbatim and validated by the package itself.
|
|
#
|
|
# Prefer the extension manager over editing this list by hand:
|
|
# make extension-install SOURCE="deerflow-extension-acme==1.2.3"
|
|
# make extension-install SOURCE="git+https://github.com/acme/deerflow-extension-acme.git@<commit>"
|
|
# make extension-install SOURCE="/absolute/path/to/local-extension"
|
|
# make extension-list
|
|
# make extension-enable NAME=acme
|
|
# make extension-disable NAME=acme
|
|
# make extension-remove NAME=acme
|
|
# The installer updates backend/pyproject.toml, backend/uv.lock, and this block
|
|
# together. It also asks the operator to confirm that the source is trusted;
|
|
# extensions and their build systems execute with Gateway privileges.
|
|
#
|
|
# Every install, enable, disable, remove, or manual edit requires a Gateway
|
|
# restart. Development launchers may fetch missing locked artifacts before
|
|
# handoff; a built production Gateway never installs plugins during startup.
|
|
# Local directories are copied to backend/extensions/sources as deployable
|
|
# snapshots; use a pinned version or public HTTPS Git commit for reproducible
|
|
# remote sources. SSH Git URLs are rejected because the stock Docker builder
|
|
# does not forward host SSH credentials.
|
|
# Loading remains explicit: a package installed outside the manager does nothing
|
|
# until it is listed here, and list order fixes contribution ordering.
|
|
#
|
|
# This is deliberately separate from the `extensions:` block above (MCP servers,
|
|
# skills, config-declared middlewares). That one is backed by
|
|
# extensions_config.json, which the Gateway can rewrite through an HTTP
|
|
# endpoint; a list that causes code to be imported must stay in this
|
|
# operator-controlled file only.
|
|
#
|
|
# Plugins can contribute semantically placed middleware, task-lifecycle hooks,
|
|
# system-model observers, Gateway-lifetime services, and eager FastAPI HTTP routers.
|
|
# See examples/deerflow-extension-example for a package that exercises all five.
|
|
#
|
|
# plugins:
|
|
# - name: example # PEP 621 entry-point name
|
|
# package: deerflow-extension-example # Python distribution managed by uv
|
|
# use: deerflow_extension_example:install
|
|
# enabled: true # false skips import and registration
|
|
# required: false # true makes load failure abort Gateway startup
|
|
# # (install --required opts in)
|
|
# host_access: # optional host-owned grants (restart required)
|
|
# model_invocation:
|
|
# roles: # logical role -> configured models[].name
|
|
# default: my-configured-model
|
|
# max_concurrency: 2 # shared by this installation's services
|
|
# timeout_seconds: 60 # includes waiting for capacity
|
|
# max_input_chars: 262144 # includes the response-schema instruction
|
|
# max_output_chars: 65536 # returned text, checked after generation
|
|
# table_prefix: example_ext_ # optional: table-name prefix this extension
|
|
# # owns under its own MetaData/migration chain.
|
|
# # Keeps `alembic revision --autogenerate`, run
|
|
# # directly against a live database, from seeing
|
|
# # those tables and proposing to drop them.
|
|
# # (`make migrate-rev` diffs a throwaway SQLite
|
|
# # built from the migration chain, so it never
|
|
# # sees them in the first place.)
|
|
# # Rejected at startup if empty, or if it would
|
|
# # also match a host-owned table name.
|
|
# config:
|
|
# label: example # extension-private values, if any
|