docs: add evolution config controls (#2852)

* docs: add evolution config controls

* docs: address evolution config review
This commit is contained in:
lxowalle
2026-05-12 11:23:06 +08:00
committed by GitHub
parent 255a67e2da
commit 223ebdf0c7
12 changed files with 414 additions and 1 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ The current frontend exposes these major pages and flows:
- `/agent/tools`
- View tool availability and enable or disable tool switches through config-backed APIs.
- `/config`
- Edit agent defaults, exec controls, cron controls, heartbeat, device monitoring, launcher networking, and launch-at-login settings.
- Edit agent defaults, self-evolution, exec controls, cron controls, heartbeat, device monitoring, launcher networking, and launch-at-login settings.
- `/logs`
- View the in-memory gateway log buffer and clear it.
@@ -20,6 +20,7 @@ import {
AgentDefaultsSection,
CronSection,
DevicesSection,
EvolutionSection,
ExecSection,
LauncherSection,
MCPSection,
@@ -33,6 +34,7 @@ import {
type MCPServerForm,
buildFormFromConfig,
parseCIDRText,
parseFloatField,
parseIntField,
parseJSONObjectField,
parseMultilineList,
@@ -281,6 +283,16 @@ export function ConfigPage() {
"Cron exec timeout",
{ min: 0 },
)
const evolutionMinTaskCount = parseIntField(
form.evolutionMinTaskCount,
"Evolution minimum task count",
{ min: 1 },
)
const evolutionMinSuccessRatio = parseFloatField(
form.evolutionMinSuccessRatio,
"Evolution minimum success ratio",
{ min: 0.01, max: 1 },
)
const mcpDiscoveryValidationEnabled =
form.mcpEnabled && form.mcpDiscoveryEnabled
const mcpDiscoveryPatch: Record<string, unknown> = {
@@ -500,6 +512,20 @@ export function ConfigPage() {
session: {
dm_scope: dmScope,
},
evolution: {
enabled: form.evolutionEnabled,
mode: form.evolutionMode,
state_dir:
form.evolutionStateDir.trim() === ""
? null
: form.evolutionStateDir.trim(),
min_task_count: evolutionMinTaskCount,
min_success_ratio: evolutionMinSuccessRatio,
cold_path_trigger: form.evolutionColdPathTrigger,
cold_path_times: parseMultilineList(
form.evolutionColdPathTimesText,
),
},
tools: {
cron: {
allow_command: form.allowCommand,
@@ -661,6 +687,8 @@ export function ConfigPage() {
<RuntimeSection form={form} onFieldChange={updateField} />
<EvolutionSection form={form} onFieldChange={updateField} />
<MCPSection
form={form}
onFieldChange={updateField}
@@ -236,6 +236,152 @@ interface MCPSectionProps {
) => void
}
interface EvolutionSectionProps {
form: CoreConfigForm
onFieldChange: UpdateCoreField
}
export function EvolutionSection({
form,
onFieldChange,
}: EvolutionSectionProps) {
const { t } = useTranslation()
return (
<ConfigSectionCard
title={t("pages.config.sections.evolution")}
description={t("pages.config.evolution_section_hint")}
>
<SwitchCardField
label={t("pages.config.evolution_enabled")}
hint={t("pages.config.evolution_enabled_hint")}
layout="setting-row"
checked={form.evolutionEnabled}
onCheckedChange={(checked) =>
onFieldChange("evolutionEnabled", checked)
}
/>
<Field
label={t("pages.config.evolution_mode")}
hint={t("pages.config.evolution_mode_hint")}
layout="setting-row"
>
<Select
value={form.evolutionMode}
onValueChange={(value) => onFieldChange("evolutionMode", value)}
>
<SelectTrigger aria-label={t("pages.config.evolution_mode")}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="observe">
{t("pages.config.evolution_mode_observe")}
</SelectItem>
<SelectItem value="draft">
{t("pages.config.evolution_mode_draft")}
</SelectItem>
<SelectItem value="apply">
{t("pages.config.evolution_mode_apply")}
</SelectItem>
</SelectContent>
</Select>
</Field>
<Field
label={t("pages.config.evolution_state_dir")}
hint={t("pages.config.evolution_state_dir_hint")}
layout="setting-row"
>
<Input
value={form.evolutionStateDir}
onChange={(e) => onFieldChange("evolutionStateDir", e.target.value)}
placeholder="e.g. /var/lib/picoclaw/evolution"
/>
</Field>
<Field
label={t("pages.config.evolution_min_task_count")}
hint={t("pages.config.evolution_min_task_count_hint")}
layout="setting-row"
>
<Input
type="number"
min={1}
value={form.evolutionMinTaskCount}
onChange={(e) =>
onFieldChange("evolutionMinTaskCount", e.target.value)
}
/>
</Field>
<Field
label={t("pages.config.evolution_min_success_ratio")}
hint={t("pages.config.evolution_min_success_ratio_hint")}
layout="setting-row"
>
<Input
type="number"
min={0.01}
max={1}
step="0.05"
value={form.evolutionMinSuccessRatio}
onChange={(e) =>
onFieldChange("evolutionMinSuccessRatio", e.target.value)
}
/>
</Field>
<Field
label={t("pages.config.evolution_cold_path_trigger")}
hint={t("pages.config.evolution_cold_path_trigger_hint")}
layout="setting-row"
>
<Select
value={form.evolutionColdPathTrigger}
onValueChange={(value) =>
onFieldChange("evolutionColdPathTrigger", value)
}
>
<SelectTrigger
aria-label={t("pages.config.evolution_cold_path_trigger")}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="after_turn">
{t("pages.config.evolution_cold_path_after_turn")}
</SelectItem>
<SelectItem value="scheduled">
{t("pages.config.evolution_cold_path_scheduled")}
</SelectItem>
<SelectItem value="manual">
{t("pages.config.evolution_cold_path_manual")}
</SelectItem>
</SelectContent>
</Select>
</Field>
{form.evolutionColdPathTrigger === "scheduled" && (
<Field
label={t("pages.config.evolution_cold_path_times")}
hint={t("pages.config.evolution_cold_path_times_hint")}
layout="setting-row"
>
<Textarea
value={form.evolutionColdPathTimesText}
placeholder={"03:00\n15:30"}
className="min-h-[88px] font-mono text-xs"
onChange={(e) =>
onFieldChange("evolutionColdPathTimesText", e.target.value)
}
/>
</Field>
)}
</ConfigSectionCard>
)
}
export function MCPSection({
form,
onFieldChange,
@@ -32,6 +32,13 @@ export interface CoreConfigForm {
mcpDiscoveryUseBM25: boolean
mcpDiscoveryUseRegex: boolean
mcpServers: MCPServerForm[]
evolutionEnabled: boolean
evolutionMode: string
evolutionStateDir: string
evolutionMinTaskCount: string
evolutionMinSuccessRatio: string
evolutionColdPathTrigger: string
evolutionColdPathTimesText: string
}
export type MCPServerType = "http" | "sse" | "stdio"
@@ -121,6 +128,13 @@ export const EMPTY_FORM: CoreConfigForm = {
mcpDiscoveryUseBM25: true,
mcpDiscoveryUseRegex: false,
mcpServers: [],
evolutionEnabled: false,
evolutionMode: "observe",
evolutionStateDir: "",
evolutionMinTaskCount: "2",
evolutionMinSuccessRatio: "0.7",
evolutionColdPathTrigger: "after_turn",
evolutionColdPathTimesText: "",
}
export const EMPTY_LAUNCHER_FORM: LauncherForm = {
@@ -215,6 +229,7 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
const session = asRecord(root.session)
const heartbeat = asRecord(root.heartbeat)
const devices = asRecord(root.devices)
const evolution = asRecord(root.evolution)
const tools = asRecord(root.tools)
const mcp = asRecord(tools.mcp)
const mcpDiscovery = asRecord(mcp.discovery)
@@ -335,6 +350,29 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
? EMPTY_FORM.mcpDiscoveryUseRegex
: asBool(mcpDiscovery.use_regex),
mcpServers: mapMCPServers(mcp.servers),
evolutionEnabled:
evolution.enabled === undefined
? EMPTY_FORM.evolutionEnabled
: asBool(evolution.enabled),
evolutionMode: asString(evolution.mode) || EMPTY_FORM.evolutionMode,
evolutionStateDir:
asString(evolution.state_dir) || EMPTY_FORM.evolutionStateDir,
evolutionMinTaskCount: asNumberString(
evolution.min_task_count,
EMPTY_FORM.evolutionMinTaskCount,
),
evolutionMinSuccessRatio: asNumberString(
evolution.min_success_ratio,
EMPTY_FORM.evolutionMinSuccessRatio,
),
evolutionColdPathTrigger:
asString(evolution.cold_path_trigger) ||
EMPTY_FORM.evolutionColdPathTrigger,
evolutionColdPathTimesText: Array.isArray(evolution.cold_path_times)
? evolution.cold_path_times
.filter((value): value is string => typeof value === "string")
.join("\n")
: EMPTY_FORM.evolutionColdPathTimesText,
}
}
@@ -356,6 +394,24 @@ export function parseIntField(
return value
}
export function parseFloatField(
rawValue: string,
label: string,
options: { min?: number; max?: number } = {},
): number {
const value = Number(rawValue)
if (!Number.isFinite(value)) {
throw new Error(`${label} must be a number.`)
}
if (options.min !== undefined && value < options.min) {
throw new Error(`${label} must be >= ${options.min}.`)
}
if (options.max !== undefined && value > options.max) {
throw new Error(`${label} must be <= ${options.max}.`)
}
return value
}
export function parseCIDRText(raw: string): string[] {
if (!raw.trim()) {
return []
+22
View File
@@ -802,6 +802,27 @@
"allowed_cidrs": "Allowed Network CIDRs",
"allowed_cidrs_hint": "Only clients from these CIDR ranges can access the service. One per line or comma-separated. Leave empty to allow all.",
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"evolution_section_hint": "Let the agent learn from completed turns and prepare skill improvements.",
"evolution_enabled": "Enable Evolution",
"evolution_enabled_hint": "Record learning data for completed turns. Draft and apply modes can also generate skill updates.",
"evolution_mode": "Evolution Mode",
"evolution_mode_hint": "Observe only records data, Draft prepares candidate skills, Apply can write accepted drafts into workspace skills.",
"evolution_mode_observe": "Observe",
"evolution_mode_draft": "Draft",
"evolution_mode_apply": "Apply",
"evolution_state_dir": "State Directory",
"evolution_state_dir_hint": "Optional directory for evolution state. Leave empty to use the workspace default.",
"evolution_min_task_count": "Minimum Task Count",
"evolution_min_task_count_hint": "Minimum related tasks required before a pattern can produce a draft.",
"evolution_min_success_ratio": "Minimum Success Ratio",
"evolution_min_success_ratio_hint": "Required success ratio for clustered tasks. Use a value greater than 0 and up to 1.",
"evolution_cold_path_trigger": "Cold Path Trigger",
"evolution_cold_path_trigger_hint": "Choose when draft generation runs for eligible learning records.",
"evolution_cold_path_after_turn": "After each turn",
"evolution_cold_path_scheduled": "Scheduled",
"evolution_cold_path_manual": "Off",
"evolution_cold_path_times": "Scheduled Times",
"evolution_cold_path_times_hint": "Run times for scheduled cold-path processing. Enter one HH:MM value per line.",
"mcp_section_hint": "Configure MCP servers without editing config.json manually.",
"mcp_enabled": "Enable MCP",
"mcp_enabled_hint": "Turn MCP server integration on or off.",
@@ -835,6 +856,7 @@
"sections": {
"agent": "Agent",
"runtime": "Runtime",
"evolution": "Evolution",
"mcp": "MCP",
"exec": "Run Commands",
"cron": "Cron Tasks",
+22
View File
@@ -700,9 +700,31 @@
"allowed_cidrs": "CIDRs de Rede Permitidos",
"allowed_cidrs_hint": "Apenas clientes destes intervalos CIDR podem acessar o serviço. Um por linha ou separados por vírgula. Deixe vazio para permitir todos.",
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"evolution_section_hint": "Permite que o agente aprenda com turnos concluídos e prepare melhorias de skills.",
"evolution_enabled": "Habilitar Evolução",
"evolution_enabled_hint": "Registrar dados de aprendizado para turnos concluídos. Os modos Draft e Apply também podem gerar atualizações de skills.",
"evolution_mode": "Modo de Evolução",
"evolution_mode_hint": "Observe apenas registra dados, Draft prepara skills candidatas, Apply pode gravar drafts aceitos nas skills do workspace.",
"evolution_mode_observe": "Observe",
"evolution_mode_draft": "Draft",
"evolution_mode_apply": "Apply",
"evolution_state_dir": "Diretório de Estado",
"evolution_state_dir_hint": "Diretório opcional para o estado de evolução. Deixe vazio para usar o padrão do workspace.",
"evolution_min_task_count": "Contagem Mínima de Tarefas",
"evolution_min_task_count_hint": "Número mínimo de tarefas relacionadas antes que um padrão possa produzir um draft.",
"evolution_min_success_ratio": "Taxa Mínima de Sucesso",
"evolution_min_success_ratio_hint": "Taxa de sucesso exigida para tarefas agrupadas. Use um valor maior que 0 e até 1.",
"evolution_cold_path_trigger": "Acionador Cold Path",
"evolution_cold_path_trigger_hint": "Escolha quando a geração de drafts roda para registros de aprendizado elegíveis.",
"evolution_cold_path_after_turn": "Após cada turno",
"evolution_cold_path_scheduled": "Agendado",
"evolution_cold_path_manual": "Desligado",
"evolution_cold_path_times": "Horários Agendados",
"evolution_cold_path_times_hint": "Horários para o processamento cold-path agendado. Insira um valor HH:MM por linha.",
"sections": {
"agent": "Agente",
"runtime": "Runtime",
"evolution": "Evolução",
"exec": "Execução de Comandos",
"cron": "Tarefas Agendadas",
"launcher": "Launcher",
+22
View File
@@ -803,6 +803,27 @@
"allowed_cidrs": "允许访问网段",
"allowed_cidrs_hint": "仅允许这些 CIDR 网段的客户端访问服务。可按行或逗号分隔;留空表示允许所有来源",
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"evolution_section_hint": "让 Agent 从已完成的回合中学习,并准备技能改进。",
"evolution_enabled": "启用自进化",
"evolution_enabled_hint": "记录已完成回合的学习数据。Draft 和 Apply 模式还可以生成技能更新。",
"evolution_mode": "自进化模式",
"evolution_mode_hint": "Observe 只记录数据,Draft 生成候选技能,Apply 可将接受的草稿写入工作区技能。",
"evolution_mode_observe": "observe",
"evolution_mode_draft": "draft",
"evolution_mode_apply": "apply",
"evolution_state_dir": "状态目录",
"evolution_state_dir_hint": "自进化状态的可选目录。留空时使用工作区默认位置。",
"evolution_min_task_count": "最小任务数",
"evolution_min_task_count_hint": "一个模式能生成草稿前,至少需要多少个相关任务。",
"evolution_min_success_ratio": "最小成功率",
"evolution_min_success_ratio_hint": "聚类任务所需的成功率。取值需大于 0,且不超过 1。",
"evolution_cold_path_trigger": "冷路径触发方式",
"evolution_cold_path_trigger_hint": "选择何时为符合条件的学习记录运行草稿生成。",
"evolution_cold_path_after_turn": "每轮结束后",
"evolution_cold_path_scheduled": "定时运行",
"evolution_cold_path_manual": "关闭自动运行",
"evolution_cold_path_times": "定时运行时间",
"evolution_cold_path_times_hint": "定时冷路径处理的运行时间。每行填写一个 HH:MM。",
"mcp_section_hint": "通过可视化界面配置 MCP 服务器,无需手动编辑 config.json",
"mcp_enabled": "启用 MCP",
"mcp_enabled_hint": "开启或关闭 MCP 服务集成",
@@ -836,6 +857,7 @@
"sections": {
"agent": "智能体",
"runtime": "运行时",
"evolution": "自进化",
"mcp": "MCP",
"exec": "运行命令",
"cron": "定时任务",