100%

Vendor-neutral context control + generated docs

Goal

  • Build workflow_control/ as the vendor-neutral control SSOT.
  • Replace scattered attach_*/compose_* guidance with one ContextAssembler.
  • Keep Claude rule files as generated projections until two-client parity passes.
  • Make every LLM-visible workflow prompt a script-rendered catalog artifact over current plan/root state; no adapter, skill prose, or model may author or rewrite control text.

Self-hosting constraint

Preserve existing hard hooks during migration. Move selection authority into the engine; adapters and generated Claude projections remain consumers.

Implementation sketch

  • Create workflow_control/{prompts,rules,reminders,catalogs,schemas,clients}/; schemas include exact prompt provenance, TerminalReceipt, and ClientTransportDirective.
  • Migrate canonical units from CLAUDE.md/rules/skills with provenance; generate compatibility projections instead of copying policy.
  • Consume the engine-core selector and ContextAssembler; this section owns canonical control content, projections, adapters, and generated docs.
  • Render payload.reminders[] through one generic Banner component; mandatory directives/effects remain separate.
  • Add root-anchor, active-invocation-path, capability-boundary, return-to-parent, root-owns-closure, open-descendant, and closure-obligation mappings.
  • Inject active-execution isolation RuleUnit + exact bounded coding prompt from the same root/frame capability.
  • Route every client prompt/tool/result through the engine authorization gateway; adapters cannot authorize, reselect, rewrite context, infer plan state, or choose a transport lifecycle action.
  • Feed every provider event back to engine-core ExecutionPump. Adapters never own reconciliation or continuation; they expose delivery, control injection, generation cancellation, and typed event transport only.
  • Render the single NextActionContract verbatim with exact action, target, evidence, result contract, return target, and prohibited alternatives; reject client substitution.
  • Resolve mandatory reminders from state/event before every handoff and API response. Record delivered reminder IDs/digests on the dispatch receipt; missing required guidance blocks dispatch.
  • Render the GuidanceEnvelope on every interaction: NOW exact task/completion, NEXT successor or closed result map, BANNED enforced alternatives. Never make clients infer any field from history.
  • Support engine-pushed ReminderRefresh control events. Inject where supported; otherwise cancel/restart uncommitted generation at deadline or suspend committed work at an idempotent tool boundary, then refresh before activity.
  • Restrict each adapter’s effective tools to the selected action. Failure-triage handoffs are read-only and accept only the closed FailureDisposition schema.
  • Implement concrete Claude Code, Codex, and OpenCode adapters; all translate the same engine-issued ContextAssembly and authorization gateway. Where a client exposes goal/scheduling lifecycle APIs, the installed goal/loop performs only the exact ContinueRuntimeAPI.read: active relays current_task/next_upcoming_task, completed relays completed_report, and retry_later emits no task, second call, or lifecycle mutation. The adapter separately snapshots lifecycle facts and delivers exact script-emitted setup/cleanup tool calls and receipts.
  • Capture each client’s final effective context and reject native rules/skills that conflict with or widen the authorized assembly.
  • Enforce the spec-sec-11.4 hard ceiling of 30,000 estimated tokens for each ContextAssembly/raw model invocation. Required authority, result schema, NOW/NEXT/BANNED fields, RuleUnits, reminders, and evidence cannot be silently dropped; over-budget assembly must use references/progressive disclosure/task splitting or refuse with per-category usage.
  • Generate --docs and --explain-context from component + prompt/rule/reminder registries.
  • Preserve the now-coherent hook redirect: both block-route-edits.sh and block-status-md-read.sh must direct callers to the sanctioned read.py --summary API and never offer status.md; add a regression pin so the contradiction cannot return.
  • Verify and preserve the existing to_plan_state_dict read.py:__all__ export through the WorkflowContext migration.

Spec references

  • plans/workflow-engine-rework/spec/workflow-state-machines.md sec 1.6 (rails pillars 4+5, Plan-API docs SSOT), sec 10.2 (WorkflowContext, —docs). Research: hooks done; banners scattered; --docs/WorkflowContext missing; status.md prose clash.

Test strategy / acceptance

Matrix: selector fact x context class; lifecycle/result/timer event; client injection support; plan/target; run; task handle; runnable/quiescent turn; goal/loop absent/owned/stale/foreign x ContinueRuntimeView active/completed/retry_later x TerminalReceipt match. Pins: parity; every goal/loop trigger performs exactly one continue-runtime read; active relays current/next unchanged, completed relays its report unchanged, and retry_later emits no task/second call/lifecycle mutation while staying active; every control prompt has script/catalog provenance; every when_condition resolves to a declared fact/gate plus valid args and every while_state resolves to a workflow/state/action selector; stale/missing/wrong-class selector references reject; unprompted timer refresh; safe suspend/resume fallback; one action; prompt/tool substitution rejects; adapters cannot treat client state as plan state. BUG-07-552 is the required regression fixture: the measured 83,522-token legacy invocation must be reduced below the 30,000-token hard ceiling without dropping mandatory context, and the absolute limit is gating rather than observation-only.

Gate: sc-0ff7a001 closes the off_task_access surface enumeration and requires one script-executed negative hook pin per hard-denied surface.

Adapter gate: sc-ef778b10 proves concrete Claude Code, Codex, and OpenCode delivery plus the full three-branch ContinueRuntimeView contract.

Work Items

  • Author one generic Reminder Banner renderer + seed mapping catalog with schema validation that resolves every condition/gate argument and every workflow/state/action selector against the closed registries
  • Verify and preserve the existing block-route-edits.sh / block-status-md-read.sh redirect to read.py —summary, with a regression pin that rejects any status.md fallback
  • Verify and preserve the existing to_plan_state_dict read.py __all__ export through the WorkflowContext migration
  • Inventory and migrate existing rule, skill, next_action, hook, recurrence, and banned-action guidance into declarative reminder mappings, including the TPR complete-batch dispatch and recurrence cases
  • Render structured payload.reminders through Banner presentation on every LLM handoff and API response, and generate reminder docs from the same mapping registry
  • Resolve mandatory reminders from state/event and render structured payload.reminders through Banner presentation before every LLM handoff and API response; record delivered reminder IDs/digests on the dispatch receipt and block dispatch when required guidance is missing; generate reminder docs from the same mapping registry
  • Create vendor-neutral rule units and contextual injection mappings from CLAUDE.md and .claude/rules without making either Claude surface canonical; preserve source provenance and generate compatibility projections
  • Populate canonical prompt/rule/reminder catalogs consumed by the engine-core ContextAssembler; script-render and digest every LLM-visible control prompt from current ContextSnapshot; remove parallel assembly ownership and model/client-authored control text; fold BUG-07-552 into acceptance by measuring every emitted category, enforcing the 30,000-token hard ceiling before dispatch, preserving required units, returning an actionable over-budget refusal, and proving the formerly 83,522-token invocation passes only after progressive disclosure/reference/task-splitting correction
  • Implement concrete Claude Code, Codex, and OpenCode thin adapters that translate ContextAssembly and exact ClientTransportDirective tool calls; goal/loop payloads only call ContinueRuntimeAPI.read, relay active current_task/next_upcoming_task or completed_report unchanged, and on retry_later emit no task/second call/lifecycle mutation while staying active, with no rule reselection, prompt rewrite, state inference, or lifecycle decision
  • Implement concrete Claude Code, Codex, and OpenCode thin adapters that translate ContextAssembly and exact ClientTransportDirective tool calls, restricted to the selected action’s effective tools (failure-triage handoffs are read-only and accept only the closed FailureDisposition schema); goal/loop payloads only call ContinueRuntimeAPI.read, relay active current_task/next_upcoming_task or completed_report unchanged, and on retry_later emit no task/second call/lifecycle mutation while staying active, with no rule reselection, prompt rewrite, state inference, or lifecycle decision
  • Add root/frame/return/closure Reminders that restate immutable root, active path, exact action, resume obligations, prohibited targets, and root closeout ownership
  • Inject active-execution isolation from the same root/frame capability; route adapter tool/result requests through engine authorization and compare final effective client context
  • Inject active-execution isolation from the same root/frame capability; route adapter tool/result requests through engine authorization, feed every provider event back to engine-core ExecutionPump so adapters expose only delivery, control injection, generation cancellation, and typed event transport (never reconciliation or continuation); compare final effective client context and reject native rules/skills that conflict with or widen the authorized assembly
  • Implement proactive guidance rendering and transport controls for engine-pushed timer refresh or safe cancellation/resume; block activity unless NOW/NEXT/BANNED is complete, keep scheduling decisions in engine wiring, require goal/loop to read only ContinueRuntimeAPI and honor its active/current-next, completed/report, and retry-later/no-op branches exactly, and require script-generated client-tool arguments for setup/cleanup
  • Generated --docs derived from the registry + read.py surface (no hand-maintained file); renders per-component intent + spec_ref + changelog as the /improve-tooling pre-touch read
  • Generated --docs and --explain-context derived from the registry + read.py surface (no hand-maintained file); renders per-component intent + spec_ref + changelog as the /improve-tooling pre-touch read
  • Enumerate which off_task_access surfaces receive mechanical PreToolUse hook denial (the rails are a hook AND a banner, never a banner alone): skill-source reads of engine-migrated workflows alongside the existing plan.json denial; ship the hooks plus one negative pin per enumerated surface, and record which surfaces remain reminder+gateway-only with the progress-denial rationale

Fresh intel (regenerated)

This section is GREENFIELD (workflow_control/ does not exist on disk) but — unlike its sibling engine-core (pure greenfield) — it is anchored to THREE hard EXTERNAL invariants it must PRESERVE while collapsing a MEASURED, wide fragmentation the graph makes visible:

  • The scatter it replaces is real and large: 39 modules hand-roll attach_*/compose_* guidance, 90 runtime modules reference banner/reminder, 361 modules reference next_action. workflow_control/ is the ONE ContextAssembler + Banner + reminder-catalog those parallel emitters converge onto ([JOIN] with the plan mission “make every LLM-visible workflow prompt a script-rendered catalog artifact”).
  • The three preserve-invariants are already TESTED — the section’s job is to keep them coherent through the migration, not re-invent them: the two hooks block-route-edits.sh + block-status-md-read.sh (both already redirect to read.py --summary, both already pinned by test_block_route_edits.py / test_block_status_md_read_hook.py); and the read.py __all__ export (to_plan_state_dict + 31 siblings, pinned by test_read.py / test_exit_reasons_export_parity.py).
  • It CONSUMES engine-core’s selector + ContextAssembler + ContinueRuntimeAPI (sibling engine-core--s-e585be74, also not-started) — it is a strict downstream of engine-core, NOT a peer.

Decisions this package forces before any code is written:

  1. Read engine-core--s-e585be74 first. This section’s context.py/assembly.py consumers depend on engine-core’s WorkflowContext, SelectorEngine, ContextAssembly.guidance, and ContinueRuntimeAPI.read contracts. Building workflow_control/ against an unbuilt engine-core inverts the dependency.
  2. WorkflowContext WRAPS read.py read-only. The __all__ surface (load_plan/query_next_unblocked/query_success_criteria/to_plan_state_dict) is the SOLE scoped plan-state surface; NEVER reconstruct plan state from adapter/goal/loop/transcript. Any second plan-state reader is LEAK:scattered-knowledge.
  3. Touch NOTHING on the live serve path (self-hosting). The plan executes THROUGH route_walk.serve_next_v7 / run_state_machine; the co-change tier proves the read.py projection historically co-edits the orchestrator emit/halt machinery — a careless projection edit mutates the run’s own substrate.
  4. The generated Claude rule files are PROJECTIONS, never canonical. Migrating CLAUDE.md/.claude/rules into workflow_control/rules/ must preserve source provenance and generate compatibility projections; making a Claude surface canonical is the DESIGN failure the section exists to prevent.
  5. Every LLM-visible prompt is script-rendered + digested. No adapter, skill prose, or model authors control text; SCHEMA:unvalidated-config guards workflow.schema.json + the catalog/reminder registries; every reminder mapping resolves its condition/gate args + workflow/state/action selectors against the closed registries.
["serve_next_v7", "route_walk", "run_state_machine", "REGISTERED_HALT_STATES"]

DIG DEEPER

python -m scripts.plan_corpus.read --summary plans/workflow-engine-rework
sed -n '1,60p' plans/workflow-engine-rework/content/rails-components--s-b6c8160c.md
sed -n '1,66p' plans/workflow-engine-rework/content/engine-core--s-e585be74.md

(full dossier: rails-components—s-b6c8160c.intel.md)