Skip to content

Commit 19c725e

Browse files
authored
Merge pull request #6378 from loopx-project/codex/games-supervisor-legacy-actor
Retain accepted Todo lifecycle attribution and preserve planning/replay semantics. Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
2 parents 14112f3 + 317770b commit 19c725e

8 files changed

Lines changed: 337 additions & 9 deletions

File tree

‎loopx/control_plane/coordination/todo_update_intent.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {AuthorityStoreProtocolError, canonicalAuthorityObject, canonicalAuthorit
77
requireAuthorityStoreId} from "./authority_store_codec.ts";
88
import {normalizeRegisteredTodoAgents, normalizeTodoAgent} from "./todo_agents.ts";
99
import {canonicalTodoRecord} from "./todo_presentation.ts";
10-
import {TODO_OWNERSHIP_INTENT_FIELDS, planTodoAuthoringScope, TODO_AUTHORING_SCOPE_REQUEST_SCHEMA} from "../todos/authoring_scope.ts";
10+
import {planTodoAuthoringScope, TODO_AUTHORING_SCOPE_REQUEST_SCHEMA} from "../todos/authoring_scope.ts";
11+
import {shouldRecordTodoMutationActor} from "../todos/public_update.ts";
1112
import {normalizeNativePlanningIntent, planNativeTodoUpdate} from "../todos/native_update_plan.ts";
1213
import {decodeMonitorPollObservation, type MonitorPollObservation} from "../todos/monitor_metadata.ts";
1314
import {
@@ -156,10 +157,8 @@ export function prepareUpdatedTodo(
156157
// Preserve the public planner's legacy metadata semantics. Raw copy edits
157158
// already carry actor attribution, while planning-only updates historically
158159
// leave last_actor_agent_id untouched.
159-
const rawCopyChanged = Object.entries(input.patch).some(([field, value]) =>
160-
!Object.hasOwn(todo, field) || !canonicalAuthorityBytes(todo[field]).equals(canonicalAuthorityBytes(value))) ||
161-
input.clear_fields.some(field => Object.hasOwn(todo, field));
162-
if (rawCopyChanged || input.monitor_observation !== undefined || TODO_OWNERSHIP_INTENT_FIELDS.some(field => Object.hasOwn(input.planning_intent ?? {}, field))) {
160+
if (shouldRecordTodoMutationActor(todo, input.patch, input.clear_fields,
161+
input.planning_intent ?? {}, input.monitor_observation)) {
163162
next.last_actor_agent_id = input.actor_agent_id;
164163
}
165164
next.updated_at = input.now.toISOString().replace(/\.\d{3}Z$/u, "Z");

‎loopx/control_plane/todos/contract.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,11 @@ def _metadata_value_is_present(value: Any) -> bool:
10001000
"claimed_by must be a public-safe agent token such as codex-main-control"
10011001
),
10021002
),
1003+
_TodoMetadataField(
1004+
"last_actor_agent_id",
1005+
normalize_todo_claimed_by,
1006+
invalid_message="last_actor_agent_id must be a public-safe agent token",
1007+
),
10031008
_TodoMetadataField(
10041009
"bound_agent",
10051010
normalize_todo_bound_agent,
@@ -1247,6 +1252,7 @@ def format_todo_metadata_line(
12471252
decision_outcome: str | None = None,
12481253
decision_scope_outcomes: Any = None,
12491254
claimed_by: str | None = None,
1255+
last_actor_agent_id: str | None = None,
12501256
bound_agent: str | None = None,
12511257
goal_bound: bool | None = None,
12521258
blocks_agent: str | None = None,

‎loopx/control_plane/todos/legacy_mutation.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ def _update_goal_todo_legacy(
899899
monitor_metadata=monitor_intent["metadata"],
900900
public_context={
901901
"goal_id": goal_id, "role": target_role,
902-
"actor_agent_id": effective_agent_id,
902+
"actor_agent_id": mutation_authority.get("actor_agent_id"),
903903
"registered_agents": registered_agent_ids_from_registry(registry_path, goal_id),
904904
"monitor_observation": monitor_intent["observation"],
905905
"enforce_monitor_boundedness": enforce_monitor_boundedness,
@@ -986,7 +986,7 @@ def _complete_goal_todo_legacy(
986986
state_file: Path | None = None,
987987
dry_run: bool = False,
988988
) -> dict[str, Any]:
989-
from .line_update import apply_todo_update_to_lines, link_generated_successor_todo_ids
989+
from .line_update import apply_todo_update_to_lines, link_generated_successor_todo_ids, record_todo_mutation_actor
990990
from ..coordination.runtime_shadow_writer_adapter import begin_todo_runtime_shadow_capture, write_captured_todo_state, settle_todo_runtime_shadow_capture
991991

992992
shadow_runtime_root = effective_runtime_root(registry_path, runtime_root_arg)
@@ -1186,6 +1186,7 @@ def _complete_goal_todo_legacy(
11861186
successor_todo_ids=normalized_successor_todo_ids if successor_todo_ids is not None else None,
11871187
updated_at=updated_at,
11881188
)
1189+
record_todo_mutation_actor(lines, update_result, mutation_authority.get("actor_agent_id"))
11891190
unblock_resume, decision_scope_resolution = (
11901191
apply_completed_user_todo_lifecycle(
11911192
lines,
@@ -1319,7 +1320,7 @@ def _supersede_goal_todo_legacy(
13191320
state_file: Path | None = None,
13201321
dry_run: bool = False,
13211322
) -> dict[str, Any]:
1322-
from .line_update import apply_todo_update_to_lines, link_superseding_todo_id
1323+
from .line_update import apply_todo_update_to_lines, link_superseding_todo_id, record_todo_mutation_actor
13231324
from ..coordination.runtime_shadow_writer_adapter import begin_todo_runtime_shadow_capture, write_captured_todo_state, settle_todo_runtime_shadow_capture
13241325

13251326
if successor_todo_ids:
@@ -1387,6 +1388,7 @@ def _supersede_goal_todo_legacy(
13871388
note="superseded",
13881389
updated_at=updated_at,
13891390
)
1391+
record_todo_mutation_actor(lines, update_result, mutation_authority.get("actor_agent_id"))
13901392
registered_agents = registered_agent_ids_from_registry(registry_path, goal_id)
13911393
successor_intents = build_successor_intents(
13921394
next_agent_todo=next_agent_todo,

‎loopx/control_plane/todos/line_update.py‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,32 @@ def upsert_todo_metadata(
5050
return True
5151

5252

53+
def record_todo_mutation_actor(
54+
lines: list[str],
55+
update_result: dict[str, Any],
56+
actor: str | None,
57+
) -> None:
58+
"""Transport accepted mutation provenance inside the held writer transaction.
59+
60+
Authority remains with the existing lifecycle decision. An unchanged replay
61+
must not mint a new actor or advance the source timestamp.
62+
"""
63+
if not update_result.get("changed"):
64+
return
65+
if actor is None:
66+
return
67+
match = find_todo_block(
68+
lines, todo_id=update_result["todo_id"], role=update_result["role"],
69+
)
70+
if match is None:
71+
raise RuntimeError("Authorized Todo disappeared before provenance writeback")
72+
block = match[4]
73+
metadata_line = metadata_line_for_todo_block(block, {"last_actor_agent_id": actor})
74+
updated = upsert_todo_metadata(lines, block, metadata_line)
75+
update_result["metadata_updated"] = updated or update_result["metadata_updated"]
76+
update_result["last_actor_agent_id"] = actor
77+
78+
5379
def link_generated_successor_todo_ids(
5480
lines: list[str],
5581
*,
@@ -152,6 +178,7 @@ def _field_update_plan(
152178
**{key: block.get(key)
153179
for key in (
154180
"text",
181+
"note",
155182
"todo_id",
156183
"status",
157184
"claimed_by",
@@ -336,6 +363,7 @@ def apply_todo_update_to_lines(
336363
normalized_status = plan["normalized_status"]
337364
target_status = plan["target_status"]
338365
updates = plan["metadata_updates"]
366+
accepted_actor = updates.pop("last_actor_agent_id", None)
339367
text = updates.pop("text", text)
340368
updated_priority = updates.pop("priority", todo_priority_label(block))
341369
updates.pop("title", None)
@@ -354,11 +382,13 @@ def apply_todo_update_to_lines(
354382
metadata_line = metadata_line_for_todo_block(block, updates)
355383
metadata_updated = upsert_todo_metadata(lines, block, metadata_line)
356384
effective_metadata = parse_todo_metadata_line(metadata_line or "") or {}
357-
return serialize_todo_update_result(
385+
result = serialize_todo_update_result(
358386
role=resolved_role, section=section, todo=block.get("text"),
359387
todo_id=normalized_todo_id, status=target_status, priority=updated_priority,
360388
status_changed=status_changed, text_changed=text_changed,
361389
metadata_updated=metadata_updated, metadata=effective_metadata,
362390
monitor_poll_transition=plan.get("monitor_poll_transition"),
363391
external_wait_transition=plan.get("external_wait_transition"),
364392
)
393+
record_todo_mutation_actor(lines, result, accepted_actor)
394+
return result

‎loopx/control_plane/todos/public_update.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,22 @@ import {
1515
} from "./decision_metadata.ts";
1616

1717
import { normalizeMonitorConfiguration, validateMonitorConfigurationTarget } from "./monitor_metadata.ts";
18+
import {canonicalAuthorityBytes} from "../coordination/authority_store_codec.ts";
1819

1920
export const TODO_PUBLIC_UPDATE_REQUEST_SCHEMA = "todo_public_update_request_v0";
2021

22+
/** Shared provenance intent; callers still suppress unchanged replay and own admission. */
23+
export function shouldRecordTodoMutationActor(
24+
todo: JsonObject, patch: JsonObject, clearFields: readonly string[],
25+
planningIntent: JsonObject, monitorObservation: unknown,
26+
): boolean {
27+
const rawCopyChanged = Object.entries(patch).some(([field, value]) =>
28+
!Object.hasOwn(todo, field) || !canonicalAuthorityBytes(todo[field]).equals(canonicalAuthorityBytes(value))) ||
29+
clearFields.some(field => Object.hasOwn(todo, field));
30+
return rawCopyChanged || monitorObservation !== undefined ||
31+
TODO_OWNERSHIP_INTENT_FIELDS.some(field => Object.hasOwn(planningIntent, field));
32+
}
33+
2134
const SCOPE_INTENT_FIELDS = ["task_class", "status", "claimed_by", "bound_agent", "goal_bound",
2235
"blocks_agent", "clear_blocks_agent", "global_gate", "clear_global_gate", "excluded_agents",
2336
"task_repository", "task_domain", "resume_when", "clear_resume_when", "clear_claim"] as const;
@@ -100,5 +113,13 @@ export function planPublicTodoUpdate(value: unknown): JsonObject {
100113
monitor_context: {metadata: intent.monitor_metadata ?? null,
101114
observation: context.monitor_observation ?? null, role, task_class: scope.task_class,
102115
resume_when: scope.effective_resume_when, enforce_boundedness: context.enforce_monitor_boundedness}});
116+
const copyPatch = Object.fromEntries(["text", "note"].flatMap(field =>
117+
Object.hasOwn(intent, field) ? [[field, intent[field]]] : []));
118+
if (context.actor_agent_id != null && shouldRecordTodoMutationActor(
119+
todo, copyPatch, [], intent, context.monitor_observation ?? undefined)) {
120+
// The legacy writer transports this after its material-change check; actor
121+
// provenance alone must never turn an unchanged replay into a mutation.
122+
plan.metadata_updates.last_actor_agent_id = context.actor_agent_id;
123+
}
103124
return {...plan, ...(transition ? {external_wait_transition: transition} : {})};
104125
}

0 commit comments

Comments
 (0)