From 3a49e7e0178789744155616ec8558cc4809ce971 Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Tue, 13 Oct 2026 16:45:12 +0800 Subject: [PATCH 1/3] Allow Goal stride strategy to inherit live review cadence Signed-off-by: huangruiteng --- .../smoke/capability-configuration-smoke.ts | 16 +++- .../src/data/capability-configuration.ts | 26 ++++++ apps/presentation/dashboard/src/data/chat.ts | 2 +- .../goal-capability-settings.tsx | 29 ++++--- .../replan-cadence.mjs | 10 ++- loopx/capabilities/configuration_ui.py | 23 ++++++ .../todo_replan_cadence/goal_configuration.py | 2 +- .../todo_replan_cadence/machine_defaults.py | 2 +- loopx/chat_goal_configuration_api.py | 3 + loopx/execution_profile.py | 7 -- ...est_machine_configuration_goal_defaults.py | 79 ++++++++++++++++++- 11 files changed, 172 insertions(+), 27 deletions(-) diff --git a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts index b587db6a0b..9b5c4e1ee2 100644 --- a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts +++ b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; -import { goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration } from "../src/data/capability-configuration.js"; +import { goalReplanCadenceEditorValue, goalReplanCadenceWriteValue, goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration } from "../src/data/capability-configuration.js"; const periodicReportEditor = { fields: [ @@ -13,6 +13,20 @@ const periodicReportEditor = { }; const storageEditor = { fields: [{ key: "new_goal_provider" }, { key: "canonical_creation" }, { key: "new_goal_handoff_mode" }] }; +const inheritedReview = { count_unit: "effective_turns", count: 3, stride_strategy: "off" }; +assert.deepEqual(goalReplanCadenceEditorValue({ stride_strategy: "evidence_conditioned_v1" }, inheritedReview), + { ...inheritedReview, stride_strategy: "evidence_conditioned_v1" }, + "strategy-only Goal settings display the live review interval"); +assert.deepEqual(goalReplanCadenceWriteValue({ ...inheritedReview, stride_strategy: "evidence_conditioned_v1" }, undefined, inheritedReview), + { stride_strategy: "evidence_conditioned_v1" }, + "changing only the strategy must not pin the displayed interval"); +assert.deepEqual(goalReplanCadenceWriteValue({ ...inheritedReview, count: 2, stride_strategy: "evidence_conditioned_v1" }, undefined, inheritedReview), + { count_unit: "effective_turns", count: 2, stride_strategy: "evidence_conditioned_v1" }, + "an edited interval remains an explicit Goal override"); +assert.deepEqual(goalReplanCadenceWriteValue({ count_unit: "completed_todos", count: 2, stride_strategy: "off" }, + undefined, { schema_version: "todo_replan_cadence_machine_defaults_v0", completed_todos: 2 }), + { stride_strategy: "off" }, + "legacy device units can be inherited without pinning their count"); const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "sqlite", canonical_creation: true, new_goal_handoff_mode: "hard_lease" }; assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, diff --git a/apps/presentation/dashboard/src/data/capability-configuration.ts b/apps/presentation/dashboard/src/data/capability-configuration.ts index c87ceb214e..7c4a6ead4d 100644 --- a/apps/presentation/dashboard/src/data/capability-configuration.ts +++ b/apps/presentation/dashboard/src/data/capability-configuration.ts @@ -61,6 +61,32 @@ export function replanCadenceEditorValue(value: unknown): unknown { return { ...record, stride_strategy: record.stride_strategy ?? "off" }; } +/** Show the live interval when a Goal stores only its strategy choice. */ +export function goalReplanCadenceEditorValue(current: unknown, effective: unknown): Record { + const inherited = configurationObject(replanCadenceEditorValue(effective)); + return current == null ? inherited : { + ...inherited, + ...configurationObject(replanCadenceEditorValue(current)), + }; +} + +/** Keep a strategy-only Goal override on the live review interval. */ +export function goalReplanCadenceWriteValue( + draft: Record, + current: unknown, + effective: unknown, +): Record { + const saved = configurationObject(current); + const active = configurationObject(replanCadenceEditorValue(effective)); + const value = { ...draft }; + const hasSavedCadence = Object.hasOwn(saved, "count_unit") || Object.hasOwn(saved, "completed_todos"); + if (!hasSavedCadence && value.count_unit === active.count_unit && value.count === active.count) { + delete value.count_unit; + delete value.count; + } + return value; +} + /** A v0 storage selector did not enable creation; opening Settings must not. */ export function goalStorageEditorValue(value: unknown): unknown { const record = configurationObject(value); diff --git a/apps/presentation/dashboard/src/data/chat.ts b/apps/presentation/dashboard/src/data/chat.ts index 87d14c80ae..d40bd3483e 100644 --- a/apps/presentation/dashboard/src/data/chat.ts +++ b/apps/presentation/dashboard/src/data/chat.ts @@ -1700,7 +1700,7 @@ export const capabilityConfigurationCatalogSchema = z.object({ available_scopes: z.array(z.enum(["goal", "machine"])), machine_namespace: z.string().optional(), goal_feature_id: z.string().optional(), - effective_value_policy: z.literal("goal_override_over_live_machine_default").optional(), + effective_value_policy: z.enum(["goal_override_over_live_machine_default", "goal_stride_over_live_review_cadence"]).optional(), availability: z.string().optional(), default: z.record(z.string(), z.unknown()).optional(), current: z.record(z.string(), z.unknown()).optional(), diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx index c2b6a86d79..854b4c404f 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx @@ -10,7 +10,7 @@ import { type GoalConfigurationPreview, type GoalConfigurationPartialWrite, } from "../../data/chat"; -import { parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorValue } from "../../data/capability-configuration"; +import { goalReplanCadenceEditorValue, goalReplanCadenceWriteValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration } from "../../data/capability-configuration"; import { useWorkspaceI18n } from "./i18n"; import { CapabilityConfigurationFields } from "./capability-configuration-fields"; import { withReportScheduleTimezone } from "./periodic-report-schedule-field"; @@ -36,8 +36,13 @@ type CapabilityMutationState = Readonly<{ preview: GoalConfigurationPreview | null; }>; -function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record) { - const writable = projectEditableCapabilityConfiguration(selected.configuration_editor, draft, selected.default); +function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record, editorMode: "guided" | "json") { + const writable = projectEditableCapabilityConfiguration( + selected.configuration_editor, draft, editorMode === "json" && selected.capability_id === "todo_replan_cadence" ? undefined : selected.default, + ); + if (selected.capability_id === "todo_replan_cadence" && editorMode === "guided") { + return goalReplanCadenceWriteValue(writable, selected.current, selected.effective_configuration?.configuration); + } if (selected.capability_id === "pull_request_review" && !Object.hasOwn(selected.current ?? {}, "owner_logins") && JSON.stringify(writable.owner_logins ?? []) === JSON.stringify(selected.effective_configuration?.configuration?.owner_logins ?? [])) { delete writable.owner_logins; @@ -83,7 +88,9 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ busy: null, draft: projectEditableCapabilityConfiguration( selected?.configuration_editor ?? { fields: [] }, - selected?.capability_id === "todo_replan_cadence" ? replanCadenceEditorValue(current) : current, + selected?.capability_id === "todo_replan_cadence" + ? goalReplanCadenceEditorValue(selected.current, selected.effective_configuration?.configuration) + : current, selected?.default, ), partialWrite: null, @@ -97,7 +104,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ setMutation((current) => ({ ...current, busy: "preview", partialWrite: null })); setError(null); try { - const nextPreview = await previewGoalConfiguration(goalId, selected.capability_id, configuration === null ? null : goalWriteDraft(selected, configuration)); + const nextPreview = await previewGoalConfiguration(goalId, selected.capability_id, configuration === null ? null : goalWriteDraft(selected, configuration, editorMode)); setMutation((current) => ({ ...current, preview: nextPreview })); } catch (reason) { setError(reason instanceof Error ? reason.message : t("capabilities.previewFailed")); @@ -111,7 +118,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ setMutation((current) => ({ ...current, busy: "apply" })); setError(null); try { - const writableDraft = goalWriteDraft(selected, mutation.draft); + const writableDraft = goalWriteDraft(selected, mutation.draft, editorMode); const result = await applyGoalConfiguration( goalId, selected.capability_id, @@ -148,7 +155,8 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ setJsonDraft(text); const parsed = parseEditableCapabilityJson(selected.configuration_editor, text); setMutation((current) => ({ ...current, preview: null, draft: parsed - ? projectEditableCapabilityConfiguration(selected.configuration_editor, parsed, selected.default) + ? projectEditableCapabilityConfiguration(selected.configuration_editor, parsed, + selected.capability_id === "todo_replan_cadence" ? undefined : selected.default) : current.draft })); setError(null); } @@ -244,12 +252,7 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied async function createPreview() { if (!localizedSelected || !editorAvailable || busy || !jsonValid) return; - const writableDraft = projectEditableCapabilityConfiguration( - localizedSelected.configuration_editor, - draft, - localizedSelected.default, - ); - await requestPreview(writableDraft); + await requestPreview(draft); } async function createClearPreview() { diff --git a/examples/personal-workspace-browser/replan-cadence.mjs b/examples/personal-workspace-browser/replan-cadence.mjs index 717fc60320..68cd0fa190 100644 --- a/examples/personal-workspace-browser/replan-cadence.mjs +++ b/examples/personal-workspace-browser/replan-cadence.mjs @@ -88,12 +88,20 @@ export const replanCadenceScenario = { return packet.capability_catalog.capabilities.find((row) => row.capability_id === "todo_replan_cadence"); } if (await strategy.inputValue() !== "evidence_conditioned_v1") throw new Error("Goal did not inherit strategy version"); + await strategy.selectOption("off"); + await page.getByRole("button", { name: "预览变更", exact: true }).click(); + await applyGoalPreview(); + let goalCadence = await readGoalCadence(); + if (JSON.stringify(goalCadence.current) !== JSON.stringify({ stride_strategy: "off" }) + || goalCadence.effective_configuration.configuration.count !== 3) { + throw new Error("Goal strategy-only override pinned or lost the inherited review interval"); + } await count.fill("2"); await strategy.selectOption("off"); await page.getByRole("button", { name: "预览变更", exact: true }).click(); await applyGoalPreview(); await page.getByRole("button", { name: "恢复沿用设备默认", exact: true }).waitFor(); - let goalCadence = await readGoalCadence(); + goalCadence = await readGoalCadence(); if (goalCadence.effective_configuration.source !== "goal_override" || goalCadence.current.count !== 2) throw new Error("Goal override readback failed"); if (goalCadence.current.stride_strategy !== "off") throw new Error("Goal stride opt-out failed"); if ((await request(base)).machine_configuration.namespaces.todo_replan_cadence.stride_strategy !== "evidence_conditioned_v1") throw new Error("Goal override changed machine strategy"); diff --git a/loopx/capabilities/configuration_ui.py b/loopx/capabilities/configuration_ui.py index 5b683ec971..df00f6677a 100644 --- a/loopx/capabilities/configuration_ui.py +++ b/loopx/capabilities/configuration_ui.py @@ -599,6 +599,29 @@ def _attach_effective_configuration(entry: dict[str, Any]) -> None: capability_default = ( entry.get("default") if isinstance(entry.get("default"), Mapping) else None ) + if ( + entry["capability_id"] == "todo_replan_cadence" + and goal_override is not None + and set(goal_override) == {"stride_strategy"} + ): + from .todo_replan_cadence.machine_defaults import ( + cadence_configuration_value, + default_replan_cadence_configuration, + ) + + # The Goal chooses the strategy while the existing review cadence stays + # live. Keep `current` sparse so readback does not claim a pinned count. + inherited_cadence = ( + cadence_configuration_value(machine_default) + if machine_default is not None + else default_replan_cadence_configuration() + ) + goal_override = { + "count_unit": inherited_cadence["count_unit"], + "count": inherited_cadence["count"], + **goal_override, + } + entry["effective_value_policy"] = "goal_stride_over_live_review_cadence" entry["effective_configuration"] = resolve_capability_configuration( str(entry["capability_id"]), goal_override=goal_override, diff --git a/loopx/capabilities/todo_replan_cadence/goal_configuration.py b/loopx/capabilities/todo_replan_cadence/goal_configuration.py index 92550ae244..4267f92751 100644 --- a/loopx/capabilities/todo_replan_cadence/goal_configuration.py +++ b/loopx/capabilities/todo_replan_cadence/goal_configuration.py @@ -25,7 +25,7 @@ def configuration_summary(goal: Mapping[str, Any]) -> dict[str, Any] | None: "count": completed_todo_replan_threshold(dict(profile)), **strategy, } - return None + return strategy or None __all__ = ["configuration_summary"] diff --git a/loopx/capabilities/todo_replan_cadence/machine_defaults.py b/loopx/capabilities/todo_replan_cadence/machine_defaults.py index e281494cfd..e0b171e481 100644 --- a/loopx/capabilities/todo_replan_cadence/machine_defaults.py +++ b/loopx/capabilities/todo_replan_cadence/machine_defaults.py @@ -175,7 +175,7 @@ def apply_todo_replan_cadence_machine_default( else "replan_after_completed_todos" ) profile[field] = cadence["count"] - if cadence.get("stride_strategy", "off") != "off": + if "stride_strategy" not in profile and cadence.get("stride_strategy", "off") != "off": profile["stride_strategy"] = cadence["stride_strategy"] projected["execution_profile"] = profile return projected diff --git a/loopx/chat_goal_configuration_api.py b/loopx/chat_goal_configuration_api.py index 30b6b5a371..a5dec8ddc5 100644 --- a/loopx/chat_goal_configuration_api.py +++ b/loopx/chat_goal_configuration_api.py @@ -243,6 +243,9 @@ def _goal_capability_options( return {"capability_improvement_configuration": config} if capability_id == "todo_replan_cadence": from .capabilities.todo_replan_cadence.machine_defaults import normalize_replan_cadence_configuration + from .control_plane.work_items.stride_policy_codec import normalize_stride_strategy + if set(config) == {"stride_strategy"}: + return {"execution_stride_strategy": normalize_stride_strategy(config["stride_strategy"])} cadence = normalize_replan_cadence_configuration(config) option = ("execution_replan_after_turns" if cadence["count_unit"] == "effective_turns" else "execution_replan_after_todos") diff --git a/loopx/execution_profile.py b/loopx/execution_profile.py index 7ead8148aa..e2beb5e700 100644 --- a/loopx/execution_profile.py +++ b/loopx/execution_profile.py @@ -157,8 +157,6 @@ def compact_execution_profile(value: Any) -> dict[str, Any]: if "stride_strategy" in value: from .control_plane.work_items.stride_policy_codec import normalize_stride_strategy profile["stride_strategy"] = normalize_stride_strategy(value["stride_strategy"]) - if not any(key in value for key in ("replan_after_effective_turns", "replan_after_completed_todos")): - raise ValueError("a Goal stride strategy requires an explicit review cadence in the same profile") if "replan_after_effective_turns" in value and "replan_after_completed_todos" in value: raise ValueError("choose one review cadence unit") @@ -291,11 +289,6 @@ def apply_goal_execution_profile_change( profile.pop("replan_after_completed_todos", None) if clear_replan_after_completed_todos: profile.pop("replan_after_completed_todos", None) - if stride_strategy is None and (clear_replan_after_completed_todos or clear_replan_after_effective_turns) and not any( - key in profile for key in ("replan_after_effective_turns", "replan_after_completed_todos") - ): - # This capability is one review configuration, not per-field inheritance. - profile.pop("stride_strategy", None) goal["execution_profile"] = configure_execution_profile( profile, turn_granularity=turn_granularity, diff --git a/tests/capabilities/test_machine_configuration_goal_defaults.py b/tests/capabilities/test_machine_configuration_goal_defaults.py index 49e9ce0b69..aaf3b47503 100644 --- a/tests/capabilities/test_machine_configuration_goal_defaults.py +++ b/tests/capabilities/test_machine_configuration_goal_defaults.py @@ -331,7 +331,7 @@ def test_product_default_and_namespace_removal_use_six_settled_turns(tmp_path, m assert _history_goal(registry_path, runtime_root)["execution_profile"]["replan_after_effective_turns"] == 6 -def test_stride_version_inherits_as_one_review_configuration_and_rolls_back(tmp_path: Path) -> None: +def test_stride_version_inherits_and_rolls_back(tmp_path: Path) -> None: from loopx.chat_goal_configuration_api import _goal_capability_options from loopx.capabilities.goal_inspection import inspect_goal_capabilities @@ -350,7 +350,7 @@ def test_stride_version_inherits_as_one_review_configuration_and_rolls_back(tmp_ configure_machine_configuration(runtime_root=runtime_root, registry=owner, configuration=configuration, execute=True, expected_plan_revision=preview["plan_revision"]) assert _history_goal(registry_path, runtime_root)["execution_profile"]["stride_strategy"] == "evidence_conditioned_v1" - # A legacy Goal override pins the whole review configuration, including off. + # A cadence-only Goal override retains the previous default-off meaning. configure_goal(registry_path=registry_path, goal_id=GOAL_ID, execution_replan_after_turns=3, execute=True) assert "stride_strategy" not in _history_goal(registry_path, runtime_root)["execution_profile"] @@ -391,6 +391,81 @@ def test_stride_version_inherits_as_one_review_configuration_and_rolls_back(tmp_ assert registry_path.read_bytes() == before +def test_goal_stride_only_uses_live_review_interval(tmp_path: Path) -> None: + from loopx.chat_goal_configuration_api import _goal_capability_options + from loopx.capabilities.goal_inspection import inspect_goal_capabilities + + _repo, registry_path, runtime_root = _fixture(tmp_path) + options = _goal_capability_options("todo_replan_cadence", { + "stride_strategy": "evidence_conditioned_v1", + }) + assert options == {"execution_stride_strategy": "evidence_conditioned_v1"} + before = registry_path.read_bytes() + preview = configure_goal(registry_path=registry_path, goal_id=GOAL_ID, **options) + assert preview["ok"] and preview["changed"] + assert registry_path.read_bytes() == before + configure_goal(registry_path=registry_path, goal_id=GOAL_ID, execute=True, **options) + raw = json.loads(registry_path.read_text())["goals"][0]["execution_profile"] + assert raw["stride_strategy"] == "evidence_conditioned_v1" + assert "replan_after_effective_turns" not in raw + assert "replan_after_completed_todos" not in raw + inherited = _history_goal(registry_path, runtime_root)["execution_profile"] + assert inherited["replan_after_completed_todos"] == 2 + assert inherited["stride_strategy"] == "evidence_conditioned_v1" + + def review() -> dict[str, object]: + inspection = inspect_goal_capabilities( + registry_path=registry_path, runtime_root=runtime_root, goal_id=GOAL_ID, + )["configuration"] + return next(row for row in inspection["capability_catalog"]["capabilities"] + if row["capability_id"] == "todo_replan_cadence") + + first = review() + assert first["current"] == {"stride_strategy": "evidence_conditioned_v1"} + assert first["effective_configuration"]["configuration"] == { + "count_unit": "completed_todos", "count": 2, + "stride_strategy": "evidence_conditioned_v1", + } + + machine_registry = build_builtin_machine_configuration_registry() + machine = _machine_configuration() + machine["namespaces"]["todo_replan_cadence"] = { + "schema_version": "todo_replan_cadence_machine_defaults_v2", + "count_unit": "effective_turns", "count": 4, "stride_strategy": "off", + } + preview = configure_machine_configuration( + runtime_root=runtime_root, registry=machine_registry, configuration=machine, + ) + configure_machine_configuration( + runtime_root=runtime_root, registry=machine_registry, configuration=machine, + execute=True, expected_plan_revision=preview["plan_revision"], + ) + projected = _history_goal(registry_path, runtime_root)["execution_profile"] + assert projected["replan_after_effective_turns"] == 4 + assert projected["stride_strategy"] == "evidence_conditioned_v1" + second = review() + assert second["current"] == first["current"] + assert second["effective_configuration"]["configuration"]["count"] == 4 + assert second["effective_configuration"]["effective_revision"] != first["effective_configuration"]["effective_revision"] + + configure_goal(registry_path=registry_path, goal_id=GOAL_ID, + execution_replan_after_turns=3, execute=True) + assert _history_goal(registry_path, runtime_root)["execution_profile"]["replan_after_effective_turns"] == 3 + assert _history_goal(registry_path, runtime_root)["execution_profile"]["stride_strategy"] == "evidence_conditioned_v1" + configure_goal(registry_path=registry_path, goal_id=GOAL_ID, + clear_execution_replan_after_turns=True, execute=True) + raw = json.loads(registry_path.read_text())["goals"][0]["execution_profile"] + assert "replan_after_effective_turns" not in raw + assert raw["stride_strategy"] == "evidence_conditioned_v1" + assert _history_goal(registry_path, runtime_root)["execution_profile"]["replan_after_effective_turns"] == 4 + + configure_goal(registry_path=registry_path, goal_id=GOAL_ID, + execution_stride_strategy="off", execute=True) + assert _history_goal(registry_path, runtime_root)["execution_profile"]["stride_strategy"] == "off" + with pytest.raises(ValueError, match="stride_strategy"): + _goal_capability_options("todo_replan_cadence", {"stride_strategy": "unknown"}) + + @pytest.mark.parametrize("schema_version, strategy", [ ("todo_replan_cadence_machine_defaults_v1", "evidence_conditioned_v1"), ("todo_replan_cadence_machine_defaults_v2", "future_v2"), From 6c8f55558771fb503cca71d56752198372c88e99 Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Tue, 13 Oct 2026 16:45:38 +0800 Subject: [PATCH 2/3] Document stride-only Goal configuration inheritance Signed-off-by: huangruiteng --- .../hierarchical-agent-stride-control-v0.md | 4 ++- ...rarchical-agent-stride-control-v0.zh-CN.md | 4 ++- .../goal-vision-replan-contract-v0.md | 25 +++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md index b696df5554..f06e8c708e 100644 --- a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md +++ b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md @@ -643,9 +643,11 @@ projection/review semantics. Both historical v1 decision shapes retain read compatibility, but cannot discharge a fresh enabled review. Frozen workers retain their exact source. Pin implementation revisions for comparisons; do not reinterpret old decisions. The policy seam can evolve when evidence warrants it, without maintaining speculative variants. -Six effective work Turns remains the review cadence, not the size of a Todo, +Six effective work Turns remains the default review cadence, not the size of a Todo, experiment or route change. Matched continuation must qualify actual adoption, target benefit and full cost before promotion or synergy claims. +Enabling the stride strategy for one Goal inherits its live review cadence; only +an explicit Goal cadence override pins a separate count or unit. Exploratory iteration may run only strategy-enabled test arms and reuse historical baselines as descriptive references. A fresh concurrent baseline is not required diff --git a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md index 525e4f3c4f..e37aaa5b12 100644 --- a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md +++ b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md @@ -550,8 +550,10 @@ revision、考虑的 finding 与适用/暂缓等理由,不建新图或第二 普通投影/复盘;两种历史 v1 决策保留原义读取兼容,不能完成新的启用策略复盘; 新提案使用 v2 数据格式,仍只有同一个可选策略,冻结 worker 保持确切源码。比较固定 实现 revision,不重释旧决定;有证据后迭代同一组件,避免维护假设性的策略变体。 -六个有效工作 Turn 仍是复盘周期,不是 Todo、实验或路线变化的大小。策略采用、 +六个有效工作 Turn 仍是默认复盘周期,不是 Todo、实验或路线变化的大小。策略采用、 目标收益、完整成本和交互增益须通过匹配接续等实验验证后再作结论。 +对单个 Goal 启用步幅策略时沿用实时复盘周期;只有显式设置 Goal 复盘周期,才固定 +该 Goal 的计数依据或数量。 敏捷探索可只跑启用策略的测试组,以历史基线作描述性参照;不要求每次迭代都 重启并发基线。这类运行用于观察路线采用与迭代收益,不单独证明因果净收益或 diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index bfcdafeba2..c99df9f928 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -376,12 +376,13 @@ Turn envelope 中保留: ### Configure and roll back the stride strategy Use the existing **Goal review and stride** capability editor, selecting device -defaults or an explicit Goal target. Choose the review unit/count and strategy, -preview once, confirm and read back the effective configuration/source. The -whole Goal review override wins over the device review configuration; clearing -it restores current device defaults. A legacy override without a strategy means -`off`, even when the device strategy is enabled. No separate capability switch -or per-field inheritance is added. +defaults or an explicit Goal target. Preview, confirm and read back the effective +configuration/source. A Goal can select only the strategy and keep inheriting +the live device review interval (six settled work Turns by default). An explicit +Goal review unit/count still pins that interval; an older cadence-only override +without a strategy remains `off`, even when the device strategy is enabled. +Clearing the interval alone retains the Goal strategy, while clearing both +restores device defaults. There is no separate stride timer. ![Versioned stride strategy in the existing capability editor](assets/stride-strategy-editor.png) @@ -392,8 +393,8 @@ installed-App adoption or research utility. CLI preview/apply and readback (replace `GOAL`): ```sh -loopx configure-goal --goal-id GOAL --execution-replan-after-turns 6 --execution-stride-strategy evidence_conditioned_v1 -loopx configure-goal --goal-id GOAL --execution-replan-after-turns 6 --execution-stride-strategy evidence_conditioned_v1 --execute +loopx configure-goal --goal-id GOAL --execution-stride-strategy evidence_conditioned_v1 +loopx configure-goal --goal-id GOAL --execution-stride-strategy evidence_conditioned_v1 --execute loopx --format json configure-goal --goal-id GOAL loopx configure-goal --goal-id GOAL --execution-stride-strategy off --execute ``` @@ -407,9 +408,11 @@ experiments, spend quota, clone a checkpoint, relax acceptance or rewrite a frozen experiment. Configuration is versioned; preregister the selected version and captured input scope for comparisons. -在既有「Goal 复盘与步幅」能力中心中选择设备默认值或明确的 Goal,设置计数依据、 -周期和策略,预览、确认并回读生效来源。Goal 整体覆盖设备配置;旧 Goal 覆盖中 -没有策略则为 off。关闭策略保留复盘周期;清除整个覆盖恢复设备默认值。 +在既有「Goal 复盘与步幅」能力中心中选择设备默认值或明确的 Goal,预览、确认并 +回读生效来源。Goal 可仅选择策略,继续沿用设备的实时复盘周期(默认每 6 个已结算 +工作 Turn);显式设置 Goal 计数依据和数量仍会固定该周期。旧的仅含周期覆盖没有 +策略时仍为 off。单独清除周期会保留 Goal 策略;两者都清除才恢复设备默认值。 +步幅策略没有第二个独立计时器。关闭策略保留复盘周期。 历史引用仍可读取。启用不会授权实验、消耗配额、复制 checkpoint、放宽验收或 改写冻结实验;比较时预先记录策略版本与输入覆盖范围。 From 6de6c0ff5a40322a5b70482db53de8662cdcc210 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:40:18 +0800 Subject: [PATCH 3/3] Keep unrelated capability JSON drafts unchanged Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../features/personal-workspace/goal-capability-settings.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx index 2e374a9625..764128ed9b 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-capability-settings.tsx @@ -172,7 +172,10 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{ function changeMode() { if (mutation.busy || !jsonValid) return; if (editorMode === "guided" && selected) { - setJsonDraft(JSON.stringify(goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride), null, 2)); + const jsonValue = selected.capability_id === "todo_replan_cadence" + ? goalWriteDraft(selected, mutation.draft, editorMode, mutation.cadenceOverride) + : mutation.draft; + setJsonDraft(JSON.stringify(jsonValue, null, 2)); } setEditorMode(editorMode === "guided" ? "json" : "guided"); setMutation((current) => ({ ...current, preview: null,