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
13 changes: 13 additions & 0 deletions docs/reference/protocols/turn-envelope-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ reader; a missing source is never replaced by a fresh guard. The existing generi
followthrough is now available with stride off; its result-class counts and
observations remain evidence, not successful-work or goal-gain verdicts.

When the bound Todo's captured Yardstick shows a proxy ordering opposite to the
authoritative measurement, the first complete Turn read and staged `review`
show the conflict, the preferred candidate, the proxy's comparison scope, and
the finding IDs for the measured pair alongside the Yardstick declaration.
The selected Todo's card takes precedence within the bounded read. The pinned
Explore read retains the full evidence. Unbound reviews keep navigation until
the Todo is selected. The read grants only evidence access.

已绑定 Todo 的标尺若与正式测量反向,完整 Turn 的首读和分段 `review` 会显示
分歧、正式测量更优的候选、代理指标的比较范围,以及实际测量发现和原标尺声明各自的 ID。
已选 Todo 的提示在有上限的读取中优先交付,原捕获的 Explore 读取保留完整证据。
待选择的复盘保留证据入口,绑定 Todo 后才交付该任务的提示。读取只提供证据访问。

Captured replans also reference Explore attachment syntax already present in the
same captured writer instead of repeating it in the entry work context. This
applies to default CLI/full recovery as well as optional delta delivery. Evidence,
Expand Down
11 changes: 8 additions & 3 deletions loopx/capabilities/explore/turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _policy(registry_path: Path, goal_id: str, runtime_root: Path | None = None)


def read_replan_explore_context(*, source_status: Mapping[str, Any], goal_id: str, agent_id: str | None,
review_pages: Any = None) -> dict[str, Any] | None:
review_pages: Any = None, preferred_yardstick_todo_id: str | None = None) -> dict[str, Any] | None:
"""Outer read adapter; the replan owner invokes it only for enabled reviews.

Unavailable IO is distinct from an empty or disabled evidence source. No
Expand All @@ -61,6 +61,7 @@ def read_replan_explore_context(*, source_status: Mapping[str, Any], goal_id: st
return explore_turn_context(
registry_path=Path(source_status["registry"]), runtime_root=Path(source_status["runtime_root"]),
goal_id=goal_id, agent_id=agent_id, include_finding_digests=True,
preferred_yardstick_todo_id=preferred_yardstick_todo_id,
**({"review_pages": review_pages} if review_pages is not None else {}))
except (OSError, ValueError, RuntimeError):
return {"ok": False, "goal_id": goal_id, "agent_id": agent_id}
Expand All @@ -70,6 +71,7 @@ def project_work_yardstick_sources(
*, events: list[dict[str, Any]], projection: Mapping[str, Any],
goal_id: str, agent_id: str, work_todos: list[dict[str, Any]],
enabled: bool, todo_id: str | None = None, result_node: str | None = None,
preferred_yardstick_todo_id: str | None = None,
) -> list[dict[str, Any]]:
"""Replay typed-owner-selected prefixes from one already-read canonical log.

Expand All @@ -82,6 +84,7 @@ def project_work_yardstick_sources(
requests = effect_runtime_result("work_item.yardstick.sources", {
"yardstick_enabled": enabled, "work_todos": work_todos,
"goal_id": goal_id, "agent_id": agent_id, "todo_id": todo_id, "result_node": result_node,
"preferred_yardstick_todo_id": preferred_yardstick_todo_id,
"findings": projection.get("findings", []),
"source_events": [{"goal_id": row["goal_id"], "event_id": row["event_id"]} for row in events],
})
Expand All @@ -108,6 +111,7 @@ def explore_turn_context(
result_node: str | None = None, result_revision: str | None = None,
include_finding_digests: bool = False,
review_pages: Any = None, todo_id: str | None = None,
preferred_yardstick_todo_id: str | None = None,
) -> dict[str, Any]:
if type(include_finding_digests) is not bool:
raise ValueError("include_finding_digests must be an explicit boolean")
Expand Down Expand Up @@ -165,7 +169,7 @@ def explore_turn_context(
if yardstick and graph:
sources = project_work_yardstick_sources(events=events, projection=projection,
goal_id=goal_id, agent_id=agent_id, work_todos=work_todos, enabled=yardstick,
todo_id=todo_id, result_node=result_node)
todo_id=todo_id, result_node=result_node, preferred_yardstick_todo_id=preferred_yardstick_todo_id)
return cast(dict[str, Any], effect_runtime_result(
"explore.turn_context",
{
Expand All @@ -175,7 +179,8 @@ def explore_turn_context(
"harness_gate": gate,
"projection": projection,
"plan": plan,
**({"yardstick_enabled": True, "work_todos": work_todos, "yardstick_sources": sources} if yardstick else {}),
**({"yardstick_enabled": True, "work_todos": work_todos, "yardstick_sources": sources,
"preferred_yardstick_todo_id": preferred_yardstick_todo_id} if yardstick else {}),
**focus,
**({"review_todos": [scopes[key] for key in sorted(page_tasks) if key in scopes]} if page_tasks else {}),
"route": route,
Expand Down
16 changes: 13 additions & 3 deletions loopx/control_plane/capabilities/explore_turn_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {PLANNING_TODO_ID} from "../work_items/planning_relations.ts";
import {EXPLORE_WRITEBACK_SUMMARY_LIMIT, exploreBeforeWorkGuidance, exploreInTurnFeedbackGuidance, exploreResultWritebackAffordance} from "./explore_result_writeback.ts";
import {EXPLORE_RESULT_PAGE_MAX, normalizeExploreResultPages} from "./explore_result_page.ts";
import {projectWorkMeasurementFacts, projectWorkYardstickCue, workYardstickTodos, WORK_YARDSTICK_DECISION_SCHEMA} from "../work_items/yardstick_decision.ts";
import {projectExploreMeasurementFeedback} from "./explore_measurements.ts";
import {projectExploreMeasurementFeedback, type ExploreMeasurementPoint} from "./explore_measurements.ts";

/** Reusable evidence and instructions in this owner's read model. Current
* selection, eligibility, source revisions and pagination stay outside this
Expand Down Expand Up @@ -374,13 +374,23 @@ export function projectExploreTurnContext(params: JsonObject): JsonObject {
...(workCues.length ? {work_yardsticks: workCues.slice(0, 3).map(cue => {
if (resultNode !== null) return cue;
const comparison = cue.search_comparison as JsonObject | undefined;
const goalPair = comparison?.goal as JsonObject | undefined;
const proxyPair = comparison?.proxy as JsonObject | undefined;
const supportingFindings = comparison?.proxy_order_reversed === true && goalPair && proxyPair
? [...new Set([goalPair.previous_best, goalPair.latest, proxyPair.previous_best, proxyPair.latest]
.map(point => (point as ExploreMeasurementPoint).finding_id))]
: undefined;
return {source_finding_id: cue.source_finding_id, node_id: cue.node_id,
todo_id: (cue.binding as JsonObject).todo_id, target: (cue.target as JsonObject).summary,
cue: (cue.next_action as JsonObject).reason, counterevidence_refs: cue.counterevidence_refs,
...(comparison ? {comparison_cue: comparison.guidance,
comparison_candidate_ref: (comparison.goal as JsonObject).preferred_candidate_ref,
comparison_candidate_ref: goalPair?.preferred_candidate_ref,
...(typeof comparison.proxy_order_reversed === "boolean"
? {proxy_order_reversed: comparison.proxy_order_reversed} : {})} : {}),
? {proxy_order_reversed: comparison.proxy_order_reversed} : {}),
...(supportingFindings ? {
comparison_finding_ids: supportingFindings,
proxy_comparison_scope: (cue.proxy as JsonObject).comparison_scope,
} : {})} : {}),
details_command: resultRead("--result-node", String(cue.node_id))};
}), omitted_work_yardsticks: Math.max(0, workCues.length - 3),
work_yardstick_command: command("turn-context", "--agent-id", agent, "--todo-id", "<todo-id>")} : {}),
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/quota/live_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ def build_live_quota_should_run_decision(
),
receipt_bound_todo_id=receipt_bound_todo_id,
requested_action_todo_id=requested_action_todo_id,
retained_action_selection_todo_id=retained_action_selection_todo_id,
receipt_bound_monitor_phase=receipt_bound_monitor_phase,
receipt_bound_replay_phase=receipt_bound_replay_phase,
receipt_bound_replan_obligation_id=receipt_bound_replan_obligation_id,
Expand Down
41 changes: 40 additions & 1 deletion loopx/control_plane/quota/progressive_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {completeGoalNarrative} from "../work_items/goal_context_source.ts";
import type {JsonObject} from "../effect_program.ts";
import {jsonObject, requireBoolean, requireJsonObject} from "../runtime_decode.ts";
import {GOAL_OUTCOMES_SCHEMA, OUTCOME_CONTEXT_NAVIGATION_SCHEMA} from "../goals/outcome_requirements.ts";
import {FULL_OUTCOME_READ_GUIDANCE, PROGRESSIVE_OUTCOME_READ_GUIDANCE}
import {FULL_OUTCOME_READ_GUIDANCE, PROGRESSIVE_OUTCOME_READ_GUIDANCE, selectedWorkTodoId}
from "../work_items/interaction_contract.ts";
import {quotaSummaryReferences, contextSourceIdentity, contextProjectionChanges,
type ProjectionSourceValues} from "./diagnostic_projection.ts";
Expand All @@ -27,6 +27,7 @@ type ReusableReadField = {
export type ReusableExactBody = {path: Path; sourceIdentity: string};
const MIN_REUSABLE_BODY_BYTES = 1024;
const SETTLEMENT_PLAN_PATH = ["interaction_contract", "cli_channel", "settlement_plan"];
const REPLAN_CONTEXT_PATH = ["autonomous_replan_obligation", "replan_context"];
const pointer = (path: Path): string => path.length ? "/" + path.map(key =>
String(key).replaceAll("~", "~0").replaceAll("/", "~1")).join("/") : "";
const at = (root: unknown, path: Path): unknown => path.reduce<unknown>((row, key) =>
Expand Down Expand Up @@ -833,6 +834,39 @@ export function initialContextReadPointers(context: JsonObject, decisionFields:
return [...opening, ...remaining];
}

/** A suggested Todo in an unbound guard cannot supply the next decision's cue. */
export function scopedReplanYardstickReversal(decision: JsonObject) {
const packet = jsonObject(decision.replan_action_packet);
const review = jsonObject(at(decision, REPLAN_CONTEXT_PATH));
const explore = jsonObject(review?.explore_basis);
const cli = jsonObject(jsonObject(decision.interaction_contract)?.cli_channel);
const identity = jsonObject(jsonObject(cli?.settlement_plan)?.identity);
const todo = selectedWorkTodoId(decision);
const agent = decision.agent_id ?? jsonObject(decision.agent_identity)?.agent_id;
if (typeof packet?.obligation_id !== "string" || packet.obligation_id !== review?.obligation_id
|| packet.settlement_only === true || explore?.availability !== "available"
|| todo === null || cli?.selection_required === true || identity?.todo_id !== todo
|| typeof decision.goal_id !== "string" || identity.goal_id !== decision.goal_id
|| typeof agent !== "string" || identity.agent_id !== agent
|| !Array.isArray(explore.work_yardsticks)) return undefined;

const index = explore.work_yardsticks.findIndex(value => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 全局裁剪前保留已绑定 Todo。

File 和 SQLite 各写入四个 Todo 的可比反转。前三个需要当前 Host 缺少的能力,第四个经实际选择命令获得 Todo 结算身份。任务级 Explore 能读到反转,但原捕获只含前三项,首读、分段 review 和原捕获的 Explore 均漏掉第四项。在现有 Explore/Stride owner 应用全局三条上限前,保留已绑定 Todo 的证据。保持同一捕获和原有有界导航。

const cue = jsonObject(value);
return cue?.todo_id === todo && cue.proxy_order_reversed === true
&& typeof cue.source_finding_id === "string" && typeof cue.comparison_candidate_ref === "string";
});
if (index < 0) return undefined;
const cue = explore.work_yardsticks[index] as JsonObject;
const fields = ["todo_id", "source_finding_id", "comparison_candidate_ref", "proxy_order_reversed", "proxy_comparison_scope",
"fidelity_status", "paired_candidates"].filter(field =>
["string", "boolean", "number"].includes(typeof cue[field]));
if (Array.isArray(cue.comparison_finding_ids) && cue.comparison_finding_ids.length > 0
&& cue.comparison_finding_ids.length <= 4 && cue.comparison_finding_ids.every(id => typeof id === "string")) {
fields.push("comparison_finding_ids");
}
return {cue, fields, path: [...REPLAN_CONTEXT_PATH, "explore_basis", "work_yardsticks", index]};
}

/** A pending Manager Inbox read already delivers these scoped request rows. */
function managerAgendaOpeningFields(value: unknown, requiredReads: unknown[]): string[] | undefined {
const {content, ...read} = jsonObject(value) ?? {};
Expand Down Expand Up @@ -893,6 +927,7 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
// A staged replan has already replaced inline acceptance with a pinned
// review reader. Deliver its first action and ordering without writeback.
const replan = object(context.replan_action_packet);
let replanReversal: ReturnType<typeof scopedReplanYardstickReversal>;
if (context.effective_action === EffectiveAction.AUTONOMOUS_REPLAN_REQUIRED &&
replan?.schema_version === "replan_action_packet_v0" && replan.decision === "replan_required" &&
replan.settlement_only !== true && typeof replan.review === "string" && replan.review) {
Expand All @@ -918,6 +953,7 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
include("autonomous_replan_obligation", "replan_context", "context_id");
if (Array.isArray(obligation.triggers)) obligation.triggers.forEach((_trigger, index) =>
include("autonomous_replan_obligation", "triggers", index, "kind"));
replanReversal = scopedReplanYardstickReversal(context);
}
}
include("agent_lane_next_action", "next_step");
Expand Down Expand Up @@ -952,6 +988,9 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
// Deliver the opted-in decision cue before work, while the candidate or
// test can still change. The template and evidence stay on their owner read.
const cueStart = pointers.length;
if (replanReversal) {
for (const field of replanReversal.fields) include(...replanReversal.path, field);
}
if (context.effective_action === EffectiveAction.NORMAL_RUN && context.normal_delivery_allowed === true
&& typeof selectedTodo?.todo_id === "string" && typeof context.goal_id === "string" && typeof agentId === "string"
&& (identityAgent === undefined || identityAgent === agentId)) {
Expand Down
9 changes: 6 additions & 3 deletions loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {EXECUTION_PREPARATION_SCHEMA_VERSION} from "../work_items/execution_preparation.ts";
import {capturedContextSelection} from "./context_selection.ts";
import {contextReadSectionDigests, initialContextReadPointers, prepareProgressiveContext, prepareProgressiveContextDelivery,
progressiveContextPlan, type PreparationScopeDetail, type ReusableExactBody} from "./progressive_context.ts";
progressiveContextPlan, scopedReplanYardstickReversal,
type PreparationScopeDetail, type ReusableExactBody} from "./progressive_context.ts";
import type { LoopXTurnRouteValue } from "../turn_driver/turn_contract_generated.ts";
import { EFFECTIVE_ACTIONS, EffectiveAction, type QuotaEffectiveActionValue } from "./effective_action.generated.ts";
import { AGENT_SCOPE_FRONTIER_ACTIONS } from "../agents/agent_scope_frontier.generated.ts";
Expand Down Expand Up @@ -967,6 +968,8 @@ function projectReplanView(request: JsonObject, payload: JsonObject, sourceHash:
retain(follow, originalFollow, [["recorded_progress_count", "observation_count"]]);
} else retain(follow, originalFollow, followFields);
const explore = jsonObject(context.explore_basis) ?? {};
const yardstickCount = Array.isArray(explore.work_yardsticks) ? explore.work_yardsticks.length : 0;
const reversal = reads ? scopedReplanYardstickReversal(payload) : undefined;
const decisionValueReview = jsonObject(jsonObject(packet.stride_control)?.decision_value_review);
const review = retain({...common,
core_goal: core,
Expand All @@ -983,8 +986,8 @@ function projectReplanView(request: JsonObject, payload: JsonObject, sourceHash:
availability: explore.availability, revision: explore.revision,
...(Array.isArray(explore.measurement_feedback) && explore.measurement_feedback.length
? {measurement_count: explore.measurement_feedback.length} : {}),
...(Array.isArray(explore.work_yardsticks) && explore.work_yardsticks.length
? {yardstick_count: explore.work_yardsticks.length} : {}),
...(yardstickCount ? {yardstick_count: yardstickCount} : {}),
...(reversal ? {work_yardstick_reversal: retain(compactFields(reversal.cue, reversal.fields), reversal.cue)} : {}),
read_action: reads?.explore ?? null,
}, explore, ["availability", "revision"])}} : {}),
evidence: (Array.isArray(context.evidence) ? context.evidence : []).map(value => {
Expand Down
5 changes: 4 additions & 1 deletion loopx/control_plane/work_items/yardstick_decision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,13 @@ export function normalizeWorkYardstickSourceSnapshot(value: unknown): NonNullabl
}
/** Share current-work eligibility between the read plan and its final projection. */
export function workYardstickTodos(params: JsonObject): JsonObject[] {
return params.yardstick_enabled === true && Array.isArray(params.work_todos) ? (params.work_todos as JsonObject[])
const todos = params.yardstick_enabled === true && Array.isArray(params.work_todos) ? (params.work_todos as JsonObject[])
.filter(todo => todo.role === "agent" && !todo.done && todo.claimed_by === params.agent_id
&& ["open", "pending", "in_progress", "blocked"].includes(String(todo.status))
&& (params.todo_id == null || todo.todo_id === params.todo_id)) : [];
const preferred = todos.findIndex(todo => todo.todo_id === params.preferred_yardstick_todo_id);
if (preferred > 0) todos.unshift(...todos.splice(preferred, 1));
return todos;
}
function belongsToWork(row: JsonObject, binding: JsonObject): boolean {
const capture = row.work_yardstick as WorkYardstickCapture | undefined;
Expand Down
10 changes: 9 additions & 1 deletion loopx/quota.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,7 @@ def build_quota_should_run(
operator_inbox_urgency_projector: Callable[..., dict[str, Any]] | None = None,
receipt_bound_todo_id: str | None = None,
requested_action_todo_id: str | None = None,
retained_action_selection_todo_id: str | None = None,
receipt_bound_monitor_phase: ReceiptBoundMonitorPhase | None = None,
receipt_bound_replay_phase: ReceiptBoundReplayPhase | None = None,
receipt_bound_terminal_phase: ReceiptBoundTerminalPhase | None = None,
Expand All @@ -1060,10 +1061,17 @@ def build_quota_should_run(
build_quota_should_run as _build_quota_should_run,
)

# Inline reentry can retain an explicit choice before creating a receipt.
# This only orders the agent's eligible cards within the existing read cap.
preferred_yardstick_todo_id = receipt_bound_todo_id or requested_action_todo_id or retained_action_selection_todo_id

def read_explore_context(read_goal_id: str, read_agent_id: str | None) -> dict[str, Any] | None:
from .capabilities.explore.turn_context import read_replan_explore_context

return read_replan_explore_context(source_status=status_payload, goal_id=read_goal_id, agent_id=read_agent_id)
return read_replan_explore_context(
source_status=status_payload, goal_id=read_goal_id, agent_id=read_agent_id,
preferred_yardstick_todo_id=preferred_yardstick_todo_id,
)

return _build_quota_should_run(
status_payload,
Expand Down
Loading
Loading