7Orchestrator handover state machine.
11Provide the M11.5 audited handover operations that prevent two
12orchestrators from writing round state at the same time.
16- Emit ``role.handover.start`` and ``role.handover.complete`` operations.
17- Recover the start payload needed by accept calls.
18- Keep durable state projection inside the SQLite backend.
30- ``role.handover.start`` enters the requested/draining state.
31- ``role.handover.complete`` makes the incoming orchestrator the holder.
32- Round writes are rejected unless the durable state permits the actor.
36- json (decode the stored handover-start arguments)
37- oc_chat.registry.api (Registry that executes the handover domain commands)
38- oc_chat.registry.errors (HandoverStateError for an unknown handover)
39- oc_chat.registry.backends.base (OperationLogRow returned by both operations)
40- oc_diagnostics (structured debug logging via _dbg)
42from __future__
import annotations
45from typing
import Mapping
47from oc_diagnostics
import _dbg
54_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
63 actor_slot: str |
None =
None,
64 actor_type: str =
"orchestrator_agent",
65 command: str |
None =
None,
67 """Emit a handover-start operation."""
69 return registry.execute_domain_command(
70 "role.handover.start",
71 actor_slot
or from_slot,
74 "from_slot": from_slot,
78 actor_type=actor_type,
79 command=command
or "handover.initiate",
88 actor_type: str =
"orchestrator_agent",
89 command: str |
None =
None,
91 """Emit a handover-complete operation for ``handover_id``."""
96 state=
"unknown_handover",
97 actor_slot=actor_slot,
98 operation_type=
"role.handover.complete",
99 handover_id=handover_id,
101 from_slot = str(start[
"from_slot"])
102 to_slot = str(start[
"to_slot"])
103 started_at = str(start[
"started_at"])
104 return registry.execute_domain_command(
105 "role.handover.complete",
109 "from_slot": from_slot,
111 "started_at": started_at,
112 "completed_at": registry.clock.now_wall(),
113 "parent_operation_id": handover_id,
115 actor_type=actor_type,
116 command=command
or "handover.accept",
117 parent_operation_id=handover_id,
124) -> Mapping[str, object] |
None:
125 connect_func = getattr(registry.backend,
"connect",
None)
126 if connect_func
is None:
128 with connect_func()
as conn:
131 "SELECT ts, args_json FROM operation_log "
132 "WHERE id = ? AND operation_type = 'role.handover.start'",
137 payload = json.loads(str(row[
"args_json"]))
138 if not isinstance(payload, dict):
140 payload[
"started_at"] = row[
"ts"]
OperationLogRow accept_handover(Registry registry, *, int handover_id, str actor_slot, str actor_type="orchestrator_agent", str|None command=None)
OperationLogRow initiate_handover(Registry registry, *, str from_slot, str to_slot, str reason, str|None actor_slot=None, str actor_type="orchestrator_agent", str|None command=None)
Mapping[str, object]|None _handover_start(Registry registry, int handover_id)