Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";

import { goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults } from "../src/data/capability-configuration.js";
import { goalReplanCadenceEditorValue, goalReplanCadenceWriteValue, hasReplanCadenceOverride, goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults } from "../src/data/capability-configuration.js";

const periodicReportEditor = {
fields: [
Expand Down Expand Up @@ -29,6 +29,29 @@ assert.deepEqual(replanCadenceEditorDefaults(
{ schema_version: "todo_replan_cadence_machine_defaults_v0", completed_todos: 4 }, cadenceDefault),
{ count_unit: "completed_todos", count: 4, stride_strategy: "off" },
"legacy effective intervals retain their own unit and count");
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" }, false),
{ 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" }, true),
{ 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" },
false),
{ stride_strategy: "off" },
"legacy device units can be inherited without pinning their count");
assert.equal(hasReplanCadenceOverride({ stride_strategy: "evidence_conditioned_v1" }), false);
assert.equal(hasReplanCadenceOverride({ count_unit: "effective_turns", count: 3 }), true);
assert.equal(hasReplanCadenceOverride({ completed_todos: 2 }), true, "legacy pinned cadences stay explicit");
assert.deepEqual(goalReplanCadenceWriteValue({ ...inheritedReview, stride_strategy: "evidence_conditioned_v1" }, true),
{ ...inheritedReview, stride_strategy: "evidence_conditioned_v1" },
"an explicit interval remains pinned even when it matches the machine policy");
assert.deepEqual(goalReplanCadenceWriteValue({ count_unit: "effective_turns", count: 2, stride_strategy: "evidence_conditioned_v1" }, false),
{ stride_strategy: "evidence_conditioned_v1" },
"removing JSON interval fields clears only the cadence, including a previously pinned value");
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@ export function replanCadenceEditorDefaults(effective: unknown, fallback: unknow
return defaults;
}

/** Show the live interval when a Goal stores only its strategy choice. */
export function goalReplanCadenceEditorValue(current: unknown, effective: unknown): Record<string, unknown> {
const inherited = configurationObject(replanCadenceEditorValue(effective));
return current == null ? inherited : {
...inherited,
...configurationObject(replanCadenceEditorValue(current)),
};
}

/** Persisted or explicit JSON interval fields represent a Goal override. */
export function hasReplanCadenceOverride(value: unknown): boolean {
const record = configurationObject(value);
return ["count_unit", "count", "completed_todos"].some((key) => Object.hasOwn(record, key));
}

/** Displaying an inherited interval must not turn it into a pinned write. */
export function goalReplanCadenceWriteValue(
draft: Record<string, unknown>,
cadenceOverride: boolean,
): Record<string, unknown> {
const value = { ...draft };
if (!cadenceOverride) {
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);
Expand Down
2 changes: 1 addition & 1 deletion apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1717,7 +1717,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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type GoalConfigurationPreview,
type GoalConfigurationPartialWrite,
} from "../../data/chat";
import { parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults, replanCadenceEditorValue } from "../../data/capability-configuration";
import { goalReplanCadenceEditorValue, goalReplanCadenceWriteValue, hasReplanCadenceOverride, parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults } from "../../data/capability-configuration";
import { useWorkspaceI18n } from "./i18n";
import { CapabilityConfigurationFields } from "./capability-configuration-fields";
import { withReportScheduleTimezone } from "./periodic-report-schedule-field";
Expand All @@ -32,15 +32,19 @@ type CapabilityCatalogProps = Readonly<{
type CapabilityMutationState = Readonly<{
busy: "preview" | "apply" | null;
draft: Record<string, unknown>;
cadenceOverride: boolean;
partialWrite: GoalConfigurationPartialWrite | null;
preview: GoalConfigurationPreview | null;
}>;

function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record<string, unknown>) {
function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record<string, unknown>, editorMode: "guided" | "json", cadenceOverride: boolean) {
const defaults = selected.capability_id === "todo_replan_cadence"
? replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
? editorMode === "json" ? undefined : replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
: selected.default;
const writable = projectEditableCapabilityConfiguration(selected.configuration_editor, draft, defaults);
if (selected.capability_id === "todo_replan_cadence" && editorMode === "guided") {
return goalReplanCadenceWriteValue(writable, cadenceOverride);
}
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;
Expand All @@ -65,6 +69,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{
const [mutation, setMutation] = useState<CapabilityMutationState>({
busy: null,
draft: {},
cadenceOverride: false,
partialWrite: null,
preview: null,
});
Expand All @@ -86,9 +91,12 @@ 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,
),
cadenceOverride: hasReplanCadenceOverride(selected?.current),
partialWrite: null,
preview: null,
});
Expand All @@ -100,7 +108,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, mutation.cadenceOverride));
setMutation((current) => ({ ...current, preview: nextPreview }));
} catch (reason) {
setError(reason instanceof Error ? reason.message : t("capabilities.previewFailed"));
Expand All @@ -114,7 +122,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, mutation.cadenceOverride);
const result = await applyGoalConfiguration(
goalId,
selected.capability_id,
Expand All @@ -141,6 +149,7 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{
draft: selected?.capability_id === "periodic_report"
? withReportScheduleTimezone(current.draft, key, value)
: { ...current.draft, [key]: value },
cadenceOverride: current.cadenceOverride || (selected?.capability_id === "todo_replan_cadence" && (key === "count_unit" || key === "count")),
preview: null,
}));
setError(null);
Expand All @@ -152,16 +161,22 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{
const parsed = parseEditableCapabilityJson(selected.configuration_editor, text);
setMutation((current) => ({ ...current, preview: null, draft: parsed
? projectEditableCapabilityConfiguration(selected.configuration_editor, parsed,
selected.capability_id === "todo_replan_cadence"
? replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
: selected.default)
: current.draft }));
selected.capability_id === "todo_replan_cadence" ? undefined : selected.default)
: current.draft,
cadenceOverride: selected.capability_id === "todo_replan_cadence" && parsed
? hasReplanCadenceOverride(parsed) : current.cadenceOverride,
}));
setError(null);
}

function changeMode() {
if (mutation.busy || !jsonValid) return;
if (editorMode === "guided") setJsonDraft(JSON.stringify(mutation.draft, null, 2));
if (editorMode === "guided" && selected) {
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,
draft: editorMode === "json" && selected?.capability_id === "todo_replan_cadence"
Expand Down Expand Up @@ -255,12 +270,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() {
Expand Down Expand Up @@ -351,6 +361,11 @@ function CapabilityCatalog({ callbacks, catalog, goalId, notification, onApplied
</div>
) : null}
{editorMode === "guided" ? <section className="personal-capability-field-summary">
{selected.effective_value_policy === "goal_stride_over_live_review_cadence" ? <p>
{locale === "zh-CN"
? `复盘周期沿用${selected.machine_current ? "设备" : "产品"}默认;步幅策略由本 Goal 设置。`
: `Review interval follows ${selected.machine_current ? "device" : "product"} defaults; this Goal sets the stride strategy.`}
</p> : null}
<CapabilityConfigurationFields
disabled={Boolean(busy)}
copy={localizedCapabilityFieldCopy(locale, localizedSelected.capability_id)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -655,9 +655,11 @@ source-bound stride decision. Typed progress, successor and terminal results
remain valid evidence/outcomes, while a bare Todo successor transition cannot
stand in for the review. This closes the decision-recording gap; it does not
establish that the proposed action was adopted or improved the Goal.
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,8 +553,10 @@ revision、考虑的 finding 与适用/暂缓等理由,不建新图或第二
启用策略的周期复盘到期后,任何结算路径都须留下绑定本次来源的 stride 决定。
类型化进展、后续任务和终止结果仍可作为证据与结果,但单独的 Todo 后续转换不能
代替复盘。这补齐决定记录闭环,不证明提案已被采用或提升了 Goal。
六个有效工作 Turn 仍是复盘周期,不是 Todo、实验或路线变化的大小。策略采用、
六个有效工作 Turn 仍是默认复盘周期,不是 Todo、实验或路线变化的大小。策略采用、
目标收益、完整成本和交互增益须通过匹配接续等实验验证后再作结论。
对单个 Goal 启用步幅策略时沿用实时复盘周期;只有显式设置 Goal 复盘周期,才固定
该 Goal 的计数依据或数量。

敏捷探索可只跑启用策略的测试组,以历史基线作描述性参照;不要求每次迭代都
重启并发基线。这类运行用于观察路线采用与迭代收益,不单独证明因果净收益或
Expand Down
33 changes: 22 additions & 11 deletions docs/reference/protocols/goal-vision-replan-contract-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,18 @@ 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. Switching between
JSON and the form preserves that choice: omitted interval fields keep inheritance,
while explicit fields remain pinned even when they equal the current device value.
A strategy-only Goal capability JSON object clears an existing interval override.
The CLI's single-field strategy option retains a stored interval; use its explicit
cadence-clear options to restore inheritance.

![Versioned stride strategy in the existing capability editor](assets/stride-strategy-editor.png)

Expand All @@ -392,8 +398,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
```
Expand All @@ -407,9 +413,14 @@ 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 策略;两者都清除才恢复设备默认值。
步幅策略没有第二个独立计时器。JSON 与表单切换保留原选择:省略周期字段继续
继承,显式字段即使等于当前设备值也保持固定。Goal 能力 JSON 仅含策略时清除
已有周期覆盖;CLI 单字段策略选项仍保留已存周期,要恢复继承须显式使用周期
清除选项。关闭策略保留复盘周期。
历史引用仍可读取。启用不会授权实验、消耗配额、复制 checkpoint、放宽验收或
改写冻结实验;比较时预先记录策略版本与输入覆盖范围。

Expand Down
Loading
Loading