Client re-anchor transports (drift recovery)
Goal
- Re-anchor a drifted session (no live engine engagement, mid-plan) back to the engine WITHOUT a user-authored prompt.
- Project ONE contract — an exact continue-runtime API read plus terminal-report relay — onto three triggers: action (off-rails hooks), read/context (an injected ReanchorGuard RuleUnit), and time-or-turn (client goal/timer transports).
- Keep the plan artifact as the domain/workflow state-machine SSOT; client transports carry no plan state and select no transition.
/goaland/loopmake exactly one continue-runtime read on every turn/tick: active relays the script-rendered current task and next upcoming task unchanged, completed relays its report unchanged, and retry-later emits no task, second call, or lifecycle mutation while leaving the transport active.- Self-terminate every client steering artifact (goal, loop tick) only after the script-driven engine emits the matching sealed terminal receipt; orphans are unrepresentable.
Layering constraint (spec sec 1.6 — recovery, never primary)
- Engine-owned ReminderScheduler + ReminderRefresh remain the SOLE primary refresh channel (spec sec 1.6 “do not rely on client-local timers” binds the primary channel). This section ships the defense-in-depth recovery layer the same spec passage mandates (“keep runnable-yield detection and idle recovery as defense in depth”) for sessions with NO live adapter channel.
- A client transport never grants authority, selects a route, carries workflow state, or decides completion. Its stable payload contains only the verbatim continue-runtime read call; on every fire the RUNTIME reads the plan, advances the state machine, and returns active current/next, completed report, or fail-closed retry-later. Goal/loop code only relays active/completed data unchanged; retry-later emits nothing and leaves it active.
- The installed continue-runtime-read payload is identity-bound, not mutable-state-bound. A transport terminates only after the API relays the state machine’s sealed completed report (TerminalReceipt) matching domain target, optional plan ID, closure ID, generation, state authority, and current authoritative-state digest; prose, tests, or model confidence never suffice.
Script-first rails (ABSOLUTE — per script-first.md §1 + workflow-ownership.md §1)
- SCRIPTS generate every prompt the LLM reads on this surface: the stable plan/root-bound transport text, goal condition, tick payload, guard line, hook deny-reason, re-entry instruction, and the continue runtime’s
current_task/next_upcoming_taskfields plus exact client-tool arguments. The LLM never authors, paraphrases, branches on, or interprets workflow text; the upcoming task is preview-only until the runtime serves it as current. - The engine emits a closed
ClientTransportDirectivenaming one lifecycle action and exact tool arguments. The LLM executes it verbatim and returns the receipt; zero state/workflow interpretation is left to the LLM. - State/workflow are on strict rails. Within an engine-selected coding or code-discovery task, the LLM remains free to inspect, reason, edit, and test as needed inside the issued capability and result contract.
- Client selection is script-resolved through the client-identity SSOT (below) — the LLM never infers which client it is running in for transport purposes.
Self-hosting constraint
- During off-path build, shadow, and pre-cutover operation, the new ReanchorGuard injection, drift-deny hook branch, and goal/loop steering transports are inert for legacy roots and any root not validated as engine-driven under its pinned engine bundle. Existing hook behavior remains unchanged on those executions.
- Activation requires a validated new-root EntryEnvelope/RootClosure whose pinned bundle contains the matching guard, hook, payload, directive, and receipt schemas; ambiguous, stale, legacy, or pre-cutover identity refuses activation rather than applying new denial behavior.
- Reversible cutover and rollback change activation only for newly admitted roots. They never switch an active root between legacy and engine control or leave an installed steering artifact without its matching bundle.
Implementation sketch
- Consume and prove observation of the ONE root-closure-owned machine-checkable TerminalReceipt (spec sec 1.7; root-closure
w-4bb72d45). It is the state machine’s completed report, emitted only after plan-domain completion and sealed root closeout; every transport stop-condition merely observes it. - Define the transport-owned stable
ReentryPayload {protocol_version, domain_target_ref, plan_id?, closure_id, state_authority, prompt_catalog_id, prompt_bytes, continue_runtime_read={name,exact_args}, accepted_report_schema=ContinueRuntimeView, payload_digest}. Consume engine-core’s closedContinueRuntimeView/ClientTransportDirectiveand root-closure’s TerminalReceipt; this section owns only payload rendering, client lifecycle reconciliation, and exact setup/cleanup tool projection.tool_callis required only for install/complete/stop and contains the exact tool name/arguments; directive provenance names the script producer, catalog entry, version, current authoritative-state digest, and rendered-byte digest. - Generate
prompt_bytesfrom one catalog entry whose mandatory fields say: invokeContinueRuntimeAPI.read(exact_args)exactly once; for an active view, feed only the exact returnedcurrent_taskandnext_upcoming_taskto the LLM unchanged; for a completed view, relay onlycompleted_report; forretry_later, feed no task, make no second API call or lifecycle change, and leave the transport active for the next trigger. Never read the plan/engine directly, infer plan state, author/paraphrase a task or control prompt, act on the upcoming preview, select a successor, or claim done without the returned matching TerminalReceipt. Adapters, skills, and transport-specific implementations carry no alternate wording. - Generate every transport prompt and directive in scripts, persist its digest/provenance, restrict the LLM to the named client tool, and reject a changed argument or substitute action.
- Client-identity SSOT — generalize the tpr-pipeline orchestrator-identity strategy (.claude/skills/tpr-pipeline/orchestrator_identity.py: env-var identity + fixed self-identification prompt fallback, script-resolved against config) into ONE shared app-wide module that determines the running client; the transport lifecycle selects the per-client transport through it; tpr-pipeline refactors onto the same SSOT (its private resolution is deleted — a second implementation is LEAK:algorithmic-duplication).
- Coordinated model-tier extension — decision 09 records that BUG-07-544 may add required model-tier mapping DATA to
ClientTransportShapeand a shared selector that consumes the adapter registry. This section retains structural ownership of the shape and all transport semantics; BUG-07-544 owns the independently released collaboration-model translation and mining consumer. The extension reuses the shared client-identity strategy and must not add a second resolver, workflow route, or lifecycle branch. - Reconcile desired transport state against the actual client lifecycle (
get_goalfor Codex; schedule-list/handle lookup for Claude). Treat scratch as a cache/receipt only. Adopt only an existing transport whose plan/root/reentry-payload digest matches; never compare the installed transport against a mutable plan-state digest. Preserve a foreign active goal/task and emitunavailableor use the universal recovery floor. - Codex CLI transport — /goal (DECIDED): agent-set and live-verified. First serve emits
installwith exactcreate_goalarguments. The script-generated objective repeatedly calls onlyContinueRuntimeAPI.read(exact_args): active relays current/next unchanged, completed relays the machine report, and retry_later emits no task/second call/lifecycle mutation while leaving the goal active. Only after completed may the script emit exactupdate_goal(status=complete)as projection acknowledgement, never/goal clear. The goal neither reads/evaluates plan state nor reports workflow completion itself. An unrelated unfinished goal is never replaced or falsely completed. - Claude Code transport — /loop tick (DECIDED): Claude
/goalis excluded from the agent path because the verified agent surface cannot invoke it; use agent-callable ScheduleWakeup/Cron tools. First serve emitsinstallwith the exact continue-runtime-read payload; every fire only calls that API: active relays current/next unchanged, completed relays the machine report, and retry_later emits no task/second call/lifecycle mutation while leaving the loop active. Only the matching TerminalReceipt permits exact stop/delete arguments. The loop neither reads/evaluates plan state nor reports workflow completion itself; model judgment and accidental failure to reschedule never constitute completion. - OpenCode transport — explicit universal-floor-only disposition until empirical verification proves an agent-callable native recurrence primitive. Its native transport state is
unavailable; it receives the generated ReanchorGuard plus hard-hook recovery floor and has no owned/foreign goal/loop lifecycle cell. Any future native transport requires a recorded decision plus matrix and criterion update before cutover. - Universal guard line — ONE canonical conditional ReanchorGuard RuleUnit (“executing plan work with no served next_action envelope this turn = off-rails; re-enter via
”), rendered by the script’s ContextAssembler into every effective plan-body/skill/rule assembly. The body/skill/rule source is not rewritten and contains zero hand-authored copies; lint verifies selector coverage plus exact rendered digest. - Off-rails hook — extend the off_task_access enumeration (rails-components w-78dd0faf) with a drift deny enabled only for a validated engine-driven new root under its pinned bundle: plan-owned-path mutation while that root’s plan-execution sentinel is live and no served next_action covers the step is DENIED; the hook first validates the sentinel and activation bundle against plan/root truth, and its deny-reason carries the script-rendered root-bound re-entry command. A stale/ambiguous/legacy/pre-cutover sentinel preserves existing hook behavior and refuses new-branch activation rather than guessing root identity.
- Clients without a verified native transport (OpenCode and the gemini mirror pending verification) ship the universal floor (guard line + hooks); the per-client transport matrix lands in the generated —docs registry (rails-components).
Client transport facts (verified against vendor docs)
- Primary references: Claude Code scheduled tasks, Claude Code goals, OpenAI Follow goals, and OpenAI developer commands.
- Claude Code /loop: ticks fire into the SAME session, queued between turns (never mid-response); ticks die with the session and are restored on —resume within 7 days — drift recovery, NOT session resurrection.
- Claude Code /goal: a per-turn evaluator checks the completion condition after EVERY turn and re-surfaces its not-met reason into the transcript; the goal is updatable mid-session and survives —resume.
- Codex CLI /goal (GA 0.133.0): durable objective toward a verifiable stopping condition;
/goal <objective>+/goal pause//goal resume//goal clear; agent-sidecreate_goal,get_goal, and terminalupdate_goalwere live-verified in Codex on 2026-07-16. The client lifecycle is a projection only; the plan and sealed engine receipt remain authoritative. - Claude Code /goal is not agent-callable in the verified environment: Skill-tool invocation refused it, and a scheduled prompt containing
/goal <condition>reaches the model as plain text rather than the slash-command dispatcher. ScheduleWakeup + CronCreate/CronList/CronDelete are agent-callable. - Convergence fact: /goal and /loop project the SAME continue-runtime-read contract through different triggers (turn vs time). Each does only one API read and unchanged delivery of
current_taskplusnext_upcoming_task, or relay ofcompleted_report; neither reads plan state, selects a transition, authors workflow text, nor originates the done verdict. - Per-client binding (DECIDED, user 2026-07-16): Codex = /goal (fully autonomous — engine envelope instructs the agent to set it); Claude Code = /loop tick (fully autonomous — script-created via agent-invocable ScheduleWakeup/Cron; /goal excluded as user-typed-only, script-first over manual-step); OpenCode = native transport
unavailable, generated universal floor only until verification. The spike (w-6c349f05) verifies availability without inventing a transport. - Engine-instructed transport setup is the SANCTIONED form of self-scheduling: the envelope’s set-transport next_action owns the wakeup/cron/goal setup; a Claude-initiated ScheduleWakeup/CronCreate outside a served envelope remains banned (workflow-ownership.md §2 self-scheduled-reentry).
Spec references
- plans/workflow-engine-rework/spec/workflow-state-machines.md sec 1.6 (script/prompt authority, ReminderScheduler primary channel, client re-anchor observer contract), sec 1.7 (root closeout and TerminalReceipt home), sec 10.1 (transport gates/action vocabulary), sec 10.2 (closed contracts and client adapter boundary).
Test strategy / acceptance
- Matrix: transport (goal / loop-tick / guard-line / hook) x root activation (validated engine new root / legacy / pre-cutover / stale / ambiguous / rolled-back-new-admission) x client lifecycle (absent / owned / stale / foreign) x session state (on-rails / drifted-idle / drifted-acting / session-restored) x plan state (active / blocked / closed-elsewhere / sealed).
- Pins: drifted engine session re-anchors without a user-authored prompt; each goal/loop trigger performs exactly one continue-runtime read and no direct plan/engine read; an active response carries complete identity and relays byte-identical
current_task/next_upcoming_task, while the preview cannot execute early; bounded runtime retry exhaustion returnsretry_later, emits no task or lifecycle mutation, makes no second call that trigger, and leaves transport active; OpenCode deterministically selectsunavailableplus the universal floor; legacy/pre-cutover mutation preserves existing hook behavior and cannot install new steering; an engine-owned uncovered mutation is denied with the root-bound command; every required ReentryPayload prompt field and LLM tool argument matches a script-produced digest; alternate adapter/skill/model control prose rejects; foreign steering is preserved; nonterminal state keeps the transport active; only a matching sealed terminal receipt emits complete/stop; sealed receipt resolution verifiesexecution_state_digest; stale/mismatched/unsealed receipt-shaped output, tests-green output, plan-status prose, and model confidence cannot terminate; identical replay of the real receipt is idempotent; ReanchorGuard selector/digest lint rejects missing or divergent effective-context injection; hook deny-reason carries the verbatim re-entry command and refuses stale/ambiguous root identity. - Acceptance probe: scripts/workflow_engine/tests/test_drift_reanchor.py (cited by the plan-wide functional criterion).
Gate: sc-c071a001 proves the absolute goal/loop boundary: exactly one continue-runtime read, complete-identity byte-identical current/next delivery, fail-closed retry-later behavior, no direct state access, and terminal-report relay only.
Work Items
- Verify the decided per-client transports empirically: Codex agent-side create/get/complete plus user pause/resume/clear, owned-versus-foreign goal reconciliation, repeated exact ContinueRuntimeAPI.read plus unchanged current/next delivery, and sealed-terminal-receipt recognition; Claude Code /loop tick behavior under run-intent + autopilot-continue-guard continuation ownership and across permission prompts; OpenCode/gemini/web/VS Code availability with unavailable/native-absent mapped to the universal floor; record the verified per-client matrix in a decisions/ sidecar
- Consume the root-closure-owned machine-checkable TerminalReceipt and prove transport observation only: it is emitted only after plan-domain completion plus sealed root obligations, sealed resolution verifies authoritative and execution-state digests, and every stop-condition accepts only the exact matching receipt
- Generalize the tpr-pipeline orchestrator-identity strategy (.claude/skills/tpr-pipeline/orchestrator_identity.py: env-var identity + fixed self-identification prompt fallback, script-resolved against config) into ONE app-wide client-identity SSOT module the engine consumes to determine the running client; the transport lifecycle selects goal-vs-loop through it; refactor tpr-pipeline to consume the same SSOT and delete its private resolution (a second implementation is LEAK:algorithmic-duplication)
- Generalize the tpr-pipeline orchestrator-identity strategy (.claude/skills/tpr-pipeline/orchestrator_identity.py: env-var identity + fixed self-identification prompt fallback, script-resolved against config) into ONE app-wide client-identity SSOT module the engine consumes to determine the running client; the transport lifecycle selects goal-vs-loop through it; refactor tpr-pipeline to consume the same SSOT and delete its private resolution (a second implementation is LEAK:algorithmic-duplication). Extract the identity STRATEGY only — env-var identity plus self-identification-prompt fallback; do NOT fold scripts/tpr_review_runtime/configuration.py’s sibling load_reviewer_config/load_prompt_composer lazy loaders into the shared module even though they share the same load_module_by_path shape (STRUCTURE:absorption-overload) — those remain tpr-runtime-local, unrelated concerns.
- Implement script-owned ReentryPayload rendering plus transport reconciliation while consuming engine-core’s ContinueRuntimeView/ClientTransportDirective: the payload can only call ContinueRuntimeAPI.read with exact args; verify complete active identity, unchanged current_task/next_upcoming_task relay, fail-closed retry_later with no second call/task/lifecycle mutation, and completed-report identity without regenerating any view; emit exact install/keep/complete-goal/stop-loop/unavailable client-tool projections and treat scratch as receipt-only
- Author the canonical off-rails ReanchorGuard RuleUnit + ContextAssembler selector for every effective plan-body/skill/rule assembly; lint fails on missing selector coverage or rendered-digest divergence, and source artifacts contain zero hand-authored copies
- Extend the off_task_access hook enumeration with the engine-new-root-only drift deny: validate EntryEnvelope/RootClosure/bundle activation plus the plan-execution sentinel against plan/root truth, deny an engine-owned uncovered plan mutation with the script-rendered root-bound re-entry command, preserve existing hook behavior for legacy/pre-cutover/rollback-excluded roots, and refuse stale/ambiguous identity (coordinate with rails-components w-78dd0faf)
- Ship the decided per-client transport binding through the client-identity SSOT: Codex CLI = agent-callable goal tools whose objective only reads ContinueRuntimeAPI and relays its current/next task view, with post-TerminalReceipt projection acknowledgement; Claude Code = agent-callable ScheduleWakeup/Cron loop with the same read/relay payload and post-receipt stop directive; OpenCode and gemini mirror = explicit unavailable/native-transport-absent universal floor pending verification; record the matrix in generated —docs
- Author scripts/workflow_engine/tests/test_drift_reanchor.py and test_continue_runtime_transport_read.py: deliberately drifted sessions re-anchor without user text; every goal/loop trigger performs exactly one ContinueRuntimeAPI.read, makes no direct plan/engine read, relays a complete-identity script-produced current_task/next_upcoming_task byte-for-byte, emits no task/second call/lifecycle mutation on retry_later, cannot act on the preview or decide/mutate state, maps OpenCode native transport unavailable to the universal floor, preserves foreign steering, and self-terminates only after relaying the matching sealed machine report whose execution digest resolves current
- Author scripts/workflow_engine/tests/test_drift_reanchor.py and test_continue_runtime_transport_read.py: deliberately drifted sessions re-anchor without user text; every goal/loop trigger performs exactly one ContinueRuntimeAPI.read, makes no direct plan/engine read, relays a complete-identity script-produced current_task/next_upcoming_task byte-for-byte, emits no task/second call/lifecycle mutation on retry_later, cannot act on the preview or decide/mutate state, maps OpenCode native transport unavailable to the universal floor, preserves foreign steering, and self-terminates only after relaying the matching sealed machine report whose execution digest resolves current. Add an explicit negative pin that the /loop-tick continuation and check_stop_against_run_intent (scripts/plan_orchestrator/autopilot_guards/run_intent.py) produce exactly ONE turn-end continuation dispatch — two continuation dispatches firing in a single turn-end MUST FAIL — proving the /loop tick extends the existing Stop-hook continuation authority rather than forking a second one.
Fresh intel (regenerated)
This section is greenfield delivery against a live, load-bearing substrate it must NOT re-derive. scripts/workflow_engine/ does not exist on disk yet — the section BUILDS the transports — but the machinery it must coexist with is already shipped and hot: the scripts/plan_orchestrator/autopilot_guards/ continuation-ownership family (check_stop_against_run_intent, detect_autopilot_context, run_intent lifecycle) and the block_wait sanctioned-self-scheduling seam (record_sanctioned_background_dispatch, arm_agent_await_for_foreground_dispatch). The section’s own body flags the collision in an ASSUMPTION (line 54): “the Claude Code /loop tick overlaps run-intent + autopilot-continue-guard Stop-hook continuation ownership; the spike decides the single continuation owner before the transport ships.”
The single most important [JOIN]: the graph shows the Stop-hook continuation authority (check_stop_against_run_intent, run_intent.py:308) is embedding-clustered with the whole run-intent guard family (_tpr_review_round_loop_live 0.913, check_background_agent_against_run_intent 0.886, _check_run_intent 0.879) — meaning a NEW /loop-tick continuation owner would be the SECOND member of a cluster that already has ONE canonical owner. Shipping a parallel Stop-hook path is a LEAK:algorithmic-duplication / STRUCTURE:single-consumer-runtime waiting to happen; the ASSUMPTION’s “single continuation owner” verdict is the design gate.
Decisions this package forces before any code is written:
- Resolve the continuation-owner ASSUMPTION FIRST (w-6c349f05 spike).
check_stop_against_run_intentalready owns “block turn-end while run-intent LIVE.” A /loop tick that also re-injects continuation must EXTEND that authority, not fork it. Do NOT author a second Stop-hook continuation path. - Route the Claude transport’s ScheduleWakeup/Cron through the existing block_wait sanctioned-background allowlist (
record_sanctioned_background_dispatch/arm_agent_await_for_foreground_dispatch, background_guard.py) — a Claude-initiatedScheduleWakeup/CronCreateOUTSIDE a served envelope is already BANNED (workflow-ownership.md §2 self-scheduled-reentry; enforced byautopilot-background-agent-guard.sh). The “engine-instructed transport setup” the section calls the SANCTIONED form (body line 48) IS this seam. - w-1c4682ac (client-identity SSOT) is a bounded refactor, not a rewrite. The graph shows
orchestrator_identityresolution is one of aload_*lazy-loader TRIPLET (load_orchestrator_identity ~ load_reviewer_config ~ load_prompt_composer, 0.92/0.88) whose ONLY consumers live inside.claude/skills/tpr-pipeline/— the blast radius is contained. Extract the identity STRATEGY (env-var + self-identification prompt fallback), delete tpr-pipeline’s private copy, done. Deleting the private resolution without a shared consumer landing first is theLEAK:algorithmic-duplicationthe section body itself names. - Consume, do not re-derive, the sibling contracts —
ContinueRuntimeView/ClientTransportDirective(engine-core s-e585be74),TerminalReceipt/root-closurew-4bb72d45(root-closure-arbitrary-depth s-be712e10 + durability s-7aad8aa5), theoff_task_accessenumerationw-78dd0faf+ universal guard line (rails-components s-b6c8160c). These sections are allnot-started— this section is a downstream CONSUMER blocked on their closed contracts.
["check_stop_against_run_intent", "detect_autopilot_context", "record_sanctioned_background_dispatch", "arm_agent_await_for_foreground_dispatch", "resume_autopilot_intent", "record_user_interrupt"]
DIG DEEPER
scripts/intel-query.sh dag-ascii workflow-engine-rework --human
scripts/intel-query.sh cypher "MATCH (s:Symbol {repo:'ori-platform'}) WHERE s.file CONTAINS 'autopilot_guards/run_intent' AND s.kind='function' RETURN s.name, s.line ORDER BY s.line" --human
(full dossier: client-reanchor-transports—s-33caf0f6.intel.md)