返回设计文档

ENGINE · 03/08

Orchestrator

三层合同图、leaf resolution、checkpoint 与 failure semantics。

真相源
docs/DESIGN_ENGINES_ORCHESTRATOR.md
读取方式
构建时本地读取
规模
2,450 行

Status: active substrate design Owner: nexrur.engines.orchestrator Related designs: docs/DESIGN.md, docs/DESIGN_ENGINES_TOOLLOOP.md, docs/DESIGN_ENGINES_CAMPAIGN.md, docs/DESIGN_SCHEMA.md, docs/DESIGN_DIAGNOSIS.md, docs/DESIGN_CLI.md Tracking: dakoolfrank/nexrur#56, dakoolfrank/nexrur#129, dakoolfrank/nexrur#131, dakoolfrank/nexrur#140, dakoolfrank/nexrur#145, dakoolfrank/nexrur#198, dakoolfrank/nexrur#233, dakoolfrank/nexrur#248, dakoolfrank/nexrur#249, dakoolfrank/nexrur#255, dakoolfrank/nexrur#262, dakoolfrank/nexrur#264, dakoolfrank/nexrur#278


1. Purpose

Orchestrator is the substrate execution graph interpreter.

It consumes app-owned orchestrator.yml contracts, walks app/subagent/module graphs recursively, resolves leaf steps to app-registered ToolLoop tools, and returns normalized execution results.

One sentence:

Orchestrator reads contracts; nested steps recurse; leaf steps execute registered ToolLoop tools.

This is the ownerless target. App skill*.py files may exist only as registered callable collections; they are not an execution graph or dispatch brain.

The active Python execution surface is intentionally narrow:

Campaign
  -> ContractGraphOrchestrator.run/resume/reset_from_step
  -> run_contract_graph
  -> ToolLoop leaf execution

The retired profile-driven Orchestrator, PipelineProfile, OpsRegistry, explore adapters, and standalone replay/reset public API are not compatibility surfaces. CLI dry-run may load and validate the graph contract, but live graph execution remains Campaign-owned.

1.1 Python package boundary

The implementation is a package, not a second execution graph:

nexrur.engines.orchestrator
  __init__.py       stable public import surface
  contract.py       contracts, generic data structures and validation
  execution.py      handoff/control/payload compilation and graph execution
  checkpoint.py     checkpoint discovery and read-only status projection
  orchestrator.py   Campaign-facing ContractGraphOrchestrator lifecycle

Dependency direction is frozen:

contract <- execution <- checkpoint <- orchestrator

checkpoint.py may read normalized runtime receipts, but graph execution must not import the Campaign-facing orchestrator.py. __init__.py only re-exports the stable API; it must not contain execution, fallback or App-specific behavior. Splitting the package is a mechanical ownership change: graph semantics, checkpoint JSON, Cycle behavior and the public nexrur.engines.orchestrator import path remain unchanged.


2. Core Rule

For every step in pipeline.step_order, Orchestrator applies exactly one rule:

if step in pipeline.step_orchestrators:
    run child orchestrator.yml recursively
else:
    resolve step as a registered tool id in toolloop.yml

If neither rule resolves the step, the graph fails clearly:

unresolved_orchestrator_step

No guessing. No name heuristics. No owner fallback. No direct import of app helper files.


3. Three Contract Levels

The same graph grammar applies at every level.

3.1 App Level

Canonical path:

<app_root>/.gemini/skills/_shared/engines/orchestrator.yml

Purpose:

  • define app-level step order;
  • define app-level dependencies;
  • point app steps to subagent/module orchestrator contracts.

Shape:

pipeline:
  graph_identity:
    role: app
    owner: example-app
    product: null
  step_order:
  - ingest
  - transform
  - publish
  - review
  step_deps:
    ingest: []
    transform: [ingest]
    publish: [transform]
    review: [publish]
  step_orchestrators:
    ingest: .gemini/skills/ingest-agent/_shared/engines/orchestrator.yml
    transform: .gemini/skills/transform-agent/_shared/engines/orchestrator.yml
    publish: .gemini/skills/publish-agent/_shared/engines/orchestrator.yml
    review: .gemini/skills/publish-agent/modules/review/_shared/engines/orchestrator.yml
  terminal_rules: {}

The app-level contract does not contain owner callables, prompt truth, schema truth, campaign policy, or ToolLoop registry data.

3.2 Subagent Level

Canonical path:

<app_root>/.gemini/skills/<subagent>/_shared/engines/orchestrator.yml

Purpose:

  • define subagent internal phase order;
  • define phase dependencies;
  • call nested module orchestrators when a phase is a module;
  • leave non-nested phases as ToolLoop leaf steps.

Shape:

pipeline:
  graph_identity:
    role: subagent
    owner: example-app/transform-agent
    product: transformed-output
  step_order:
  - nexrur_evidence_obligations
  - preprocess
  - produce
  - nexrur_core_schema
  - nexrur_step6_evidence_obligations
  - nexrur_fail2pivot
  step_deps:
    nexrur_evidence_obligations: []
    preprocess: [nexrur_evidence_obligations]
    produce: [preprocess]
    nexrur_core_schema: [produce]
    nexrur_step6_evidence_obligations: [nexrur_core_schema]
    nexrur_fail2pivot: [nexrur_step6_evidence_obligations]
  step_orchestrators:
    preprocess: .gemini/skills/transform-agent/modules/preprocess/_shared/engines/orchestrator.yml
  terminal_rules: {}

Here preprocess is not a tool id. It is a nested orchestrator phase because it is declared under step_orchestrators.

3.3 Module Level

Canonical path:

<app_root>/.gemini/skills/<subagent>/modules/<module>/_shared/engines/orchestrator.yml

Purpose:

  • define module-local execution;
  • execute module leaf tools through ToolLoop;
  • optionally recurse into deeper modules with step_orchestrators.

Shape:

pipeline:
  graph_identity:
    role: module
    owner: example-app/transform-agent/preprocess
    product: transformed-output
  step_order:
  - nexrur_evidence_obligations
  - process
  - nexrur_core_schema
  - nexrur_step6_evidence_obligations
  - nexrur_fail2pivot
  step_deps:
    nexrur_evidence_obligations: []
    process: [nexrur_evidence_obligations]
    nexrur_core_schema: [process]
    nexrur_step6_evidence_obligations: [nexrur_core_schema]
    nexrur_fail2pivot: [nexrur_step6_evidence_obligations]
  terminal_rules: {}

Here all steps are leaf tool ids and must resolve in app toolloop.yml. Step ids in step_order must be unique. If the same substrate implementation is needed in two semantic moments, expose two public semantic tools, for example nexrur_evidence_obligations and nexrur_step6_evidence_obligations, instead of repeating the same tool id and relying on occurrence semantics.


4. Runtime Vocabulary

Runtime-active orchestrator contracts use a small vocabulary:

FieldMeaning
pipeline.step_orderOrdered graph surface.
pipeline.step_depsDAG dependencies.
pipeline.step_orchestratorsMapping from step name to child orchestrator.yml.

Allowed values must be human-auditable. The contract is not a second schema pack, not a tool registry, and not a campaign route file.

Forbidden in runtime-active orchestrator contracts:

  • owner
  • callable
  • steps.<step>.callable
  • step_tools
  • step_produces
  • step_consumes
  • prompt truth
  • schema truth
  • diagnosis semantics
  • campaign route / restart / halt policy
  • provider credentials

Historical documents may mention these fields, but the active ownerless target does not use them.


5. ToolLoop Leaf Resolution

Leaf steps are steps not present in pipeline.step_orchestrators.

Each leaf step must be a globally unique tool id in the app-owned ToolLoop registry:

<app_root>/.gemini/skills/_shared/engines/toolloop.yml

Example registered tool ids:

nexrur_evidence_obligations
outline_evidence
outline_framework
outline_figure
outline_plan
mirror_mirror
mirror_pdf_text_direct
nexrur_core_schema
nexrur_fail2pivot

Resolution rules:

  1. Build a global tool id index from subagents.<block>.tools.
  2. Reject duplicate tool ids across blocks.
  3. For a leaf step, find the matching tool id.
  4. Execute that tool through ToolLoopRunner / callable executor.
  5. If no tool id matches, fail the step as unresolved.

ToolLoop owns callable loading and tool execution. Orchestrator owns graph order, dependency handling, nested dispatch, and result collection.


6. Execution Algorithm

Target runner:

run_orchestrator_contract(contract_path, payload, context):
  contract = load(contract_path)
  validate_minimal_graph(contract)
  for step in contract.pipeline.step_order:
    ensure dependencies completed or blocked according to diagnosis rules

    if step in contract.pipeline.step_orchestrators:
      result = run_orchestrator_contract(child_path, child_payload, child_context)
    else:
      tool = resolve_toolloop_tool(step)
      result = run_toolloop_tool(tool, step_payload)

    normalize result envelope
    store result in graph context
    update checkpoint / trace state
  return graph envelope

The initial payload may be app/asset/run context. Orchestrator keeps the complete graph state internally, but a ToolLoop leaf receives only the compiled execution view required by the active graph contract:

  • stable app/asset/run identity from the root payload;
  • graph identity;
  • parent graph identity;
  • graph contract context: step_order, step_deps, step_orchestrators,
  • implicit diagnosis phase set;
  • one canonical aggregate of current-graph prior results: _phase_results;
  • explicit per-phase result aliases required for cross-graph dependency handoff;
  • accumulated artifact_refs;
  • failed upstream context when entering a diagnosis phase.

The following structures are Orchestrator working state and must not cross the ToolLoop leaf boundary:

  • _checkpoint_resume_phase_results;
  • _phase_result_history;
  • duplicate aggregate aliases such as step_results;
  • multiple full-result synonyms for the same Evidence result.

Checkpoint resume data is consumed before leaf payload compilation. Repeated phase execution is represented by unique graph phase identity and checkpoint receipts, not by copying an unbounded result history into every later call. Evidence exposes one canonical full-result alias, evidence_result; bounded handoff envelopes and refs remain separate because they are different contracts. This is payload compaction, not a runtime size gate: no deadline or hard payload limit is inferred from it.

The runner must not evaluate arbitrary Python expressions from YAML.

Graph-local canonical payload aliases

Canonical convenience keys such as evidence_result, step6_evidence_result, production_result, schema_result, and failed_phase_result describe prior results in the current orchestrator graph. They are not inherited input contracts.

The same graph-local boundary compiles production_summary, production_artifacts, production_failure_facts, and phase_issue_facts from explicit production-role receipts. Continuing partial/degraded Products remain issue facts even if a later Product succeeds or schema passes. Re-evidence consumes these bounded facts without an App shim or a recursive result-tree scan. Source status/reason/warnings/refs are copied, never replaced by a route. Nested graphs clear and rebuild these aliases; parent issues cannot leak into the child packet, nor can a child Step6 observation replace Product quality.

Their roles and execution semantics come from one explicit generic control envelope. Every ToolLoop registration used as an Orchestrator leaf declares a control_envelope template, and the ToolLoop boundary compiles that declaration with the returned Product receipt into this runtime shape:

control_envelope:
  version: 1
  role: production
  graph:
    disposition: continue       # continue | stop
    source_status: partial
  diagnosis:
    ready: false
    reason_code: ""
    failure_kind: ""
    facts: {}
    fields: {}
  handoff: {}

The App declaration owns role, the exact producer statuses admitted as continue or stop, the source fields copied into the generic diagnosis slots, and the explicit handoff fields. Orchestrator validates and transports the compiled envelope. It must not infer role from artifact/summary field presence, maintain a substrate-wide completed-status list, scan arbitrary result keys for diagnostic facts, or recursively search nested mappings for a Campaign handoff.

Missing, ambiguous, overlapping, or unmatched status declarations fail closed as an Orchestrator contract error. Substrate-authored runtime failures and nested graph receipts must author the same envelope directly. There is no fallback to legacy top-level field guessing.

When execution enters a nested graph, Orchestrator must remove canonical aliases inherited from the parent payload and rebuild them from the child graph's own phase_results. Parent results remain available through the parent graph receipt and parent_orchestrator; they must not occupy a child canonical alias. In particular, a child Step6 result must replace an earlier parent Step6 result so fail2pivot always consumes the current graph's diagnosis packet.

A stopped leaf whose declared control role is step6_evidence terminates its current graph after recording and notifying its failure. The normal permission to run diagnosis after an upstream Product/Evidence failure does not bypass failed Re-evidence: no usable Step6 handoff exists, and fail2pivot must not be called with absent inputs or stale handoff fields. This also applies to runtime exceptions from the declared Re-evidence leaf. The first failed phase/reason and each recorded failure remain unchanged; successful or admitted partial Re-evidence continues to diagnosis normally. Orchestrator does not weaken fail2pivot input validation or synthesize a replacement Evidence packet.

When a nested graph fails, its parent receipt preserves the leaf's raw reason_code plus bounded mechanical facts such as validation_facts, contract_violations, runtime_facts, metrics, and quality_gate. This is a fact handoff only: Orchestrator must not infer a new reason or route, and it must not inline the child graph's raw phase results or business payload.


6.1 Campaign / Trace / Cycle Identity

Orchestrator executes graph contracts, but Campaign owns campaign-level identity. The active identity contract is:

campaign_id = one CLI campaign run
trace_id    = one App-declared immutable origin/source lineage, possibly continued by multiple CLI campaign runs
asset_id    = one active business object inside that Trace; it is not the Trace identity
pipeline_run_id = one full graph execution chain under a trace
step_run_id = one executable phase attempt inside that graph
cycle_no    = one campaign-accepted continuous execution attempt inside the same Trace

Issues #262 and #264 record the design and runtime cutover for multi-asset Trace adoption. This section is the target substrate contract; existing whole-Trace bind/move behavior is an alignment gap and must not be presented as support for sequential candidate assets.

Orchestrator must accept these fields from the caller when they are present:

campaign_id: string
asset_id: string
_current_asset_id: string
trace_id: string
pipeline_run_id: string
run_id: string        # current phase attempt; equals step_run_id inside leaf ToolLoop payload
step_run_id: string
cycle_no: integer
cycle_segment: string
parent_trace_id: string | null
previous_trace_id: string | null

Rules:

  • Campaign creates a new campaign_id for each CLI run. Campaign identity alone never decides the Trace or Cycle boundary.
  • Campaign allocates a new trace_id when a fresh origin attempt starts without checkpoint/reset/adoption continuation identity. A new CLI process carrying a valid continuation identity attaches to the existing Trace/Cycle or prepares its explicitly authorized next Cycle.
  • A Checkpoint freezes the App root, orchestrator contract path, contract digest and root step_order used to create that Trace. Status projection, resume and reset must validate that exact execution identity before merging any current graph nodes. A current App graph must never inject a newly added Phase into an older Trace as pending, not_started or runnable. Contract mismatch is a read-only/fresh-run boundary, not a migration request and not a next-Cycle authorization.
  • Live Checkpoint saves for the same canonical path are serialized within the process. Atomic replacement remains mandatory; bounded retry is permitted only for Windows sharing violations (WinError 5/32) during the final replace.
  • Within one Trace, the greatest canonical cycle_no is the only writable Cycle. Once Cycle N+1 exists, every Cycle <= N is permanently sealed: status, artifacts and receipts remain readable, while resume, rerun, reset and any other Checkpoint mutation fail with historical_cycle_sealed. Selection uses the Cycle number, never file modification time.
  • A Trace is sealed only when another valid Cycle-1 Checkpoint explicitly names it through previous_trace_id. parent_trace_id records lineage but does not imply replacement. A sealed predecessor remains readable and rejects every write with trace_superseded; unrelated or merely newer Trace directories do not affect it.
  • parent_trace_id and previous_trace_id are validated opaque Trace identities and become immutable once their Checkpoint is accepted. Status projection and execution admission apply the same lifecycle decision; an HTTP adapter may hide disabled controls but cannot grant write authority.
  • If diagnosis/campaign accepts a new repair attempt for an existing asset, Orchestrator must keep the same trace_id and advance cycle_no / cycle_segment.
  • If the App authorizes candidate advancement while preserving its sealed origin set, Orchestrator must keep the same trace_id, seal the source Cycle, advance cycle_no, and transport the new asset_id plus opaque transition_intent unchanged. It must not choose the candidate, infer advancement from an asset-id change, or reuse the previous candidate's feedback outside the App-declared carry policy.
  • An App may classify part of its graph as Trace-origin production and publish a same-Trace replay floor after that origin is sealed. Orchestrator treats the resulting lifecycle disposition as opaque authorization: a same-Trace reset may target only an admitted Phase at or after that floor. A request that would replace sealed origin Products requires a fresh/new-Trace disposition and is rejected if presented as resume/reset. Substrate Python must not infer this boundary from App phase names, Product names, paths or payload fields.
  • An explicit checkpoint-backed resume is execution recovery, not a new Campaign attempt. resume + phase_path rewinds exactly that phase and its downstream state inside the existing checkpoint; it preserves trace_id, pipeline_run_id, cycle_no and cycle_segment.
  • When a caller supplies only trace_id + phase_path, checkpoint resolution selects the greatest canonical Cycle whose generic Product pointers are coherent. Each pointer must match the checkpoint trace, declare a non-future cycle, and name an artifact path with the same cycle segment. Prior-cycle pointers are accepted only in completed upstream recursive nodes preserved by a governed restart. A failed owning root may contain such a completed dependency receipt only when that receipt proves the same Trace and the immediately preceding Cycle through same_trace_restart_handoff; the Product itself may retain older provenance. Other pointers under pending/failed/reset state remain invalid. An explicit cycle addresses only that cycle and fails closed when it is incoherent. Resolution never copies or rewrites a Product to manufacture coherence.
  • resume + phase_path validates the complete recursive path before checkpoint mutation. Unknown paths, a non-child intermediate segment, an invalid leaf, or a path that conflicts with the selected root range fails closed and leaves the checkpoint unchanged.
  • When same-Cycle continuation is admitted, replaying a resume for an already-completed exact phase is an idempotent no-op. A pending, running or failed exact phase may be resumed; successful upstream dependency receipts in the same cycle remain reusable. A closed owning Subagent is not admitted into this branch and projects a next-Cycle reset instead.
  • Re-executing an already-completed exact phase is a different, explicit control (same_cycle_phase_rerun). It is same-Cycle only while the target's owning app-level Subagent graph remains mechanically open. It first proves the selected checkpoint target is completed, then treats only that target as a fresh attempt while reusing its successful same-cycle dependency closure. If the owning Subagent graph is already closed, continuation projects a next-Cycle reset instead; a caller cannot reopen a closed graph by setting the rerun flag.
  • If campaign supplies rerun_target.phase_path, the checkpoint persists it as restart_phase_path. The parent graph consumes the first segment and passes the remaining segments recursively to child orchestrators.
  • A pre-checkpoint Product Trace may be adopted only for an exact child-phase request. Orchestrator reads the App product graph identity, requires canonical same-trace/same-cycle successful Products for every direct dependency, and transports checksum-bound pointers rather than Product working memory. The adopted run writes the first canonical checkpoint in that existing cycle. This path is rejected when any checkpoint already exists, when the target is not a declared child phase, or when a Product path/identity/status is invalid.
  • Issue #275 adds a separate, explicit recover_product_dependency maintenance operation for an existing idle, writable Cycle. It repairs only a direct child dependency whose canonical node is a non-admitted failed observation. It does not run or reset any graph, admit an actual failed attempt, resurrect a missing or reset node, or override admitted descendant execution. The caller supplies one exact Product reference (including its checksum and explicit projection scope); verification uses the declared parent graph Product identity and phase role. Asset scope must name the checkpoint's explicit asset; there is no search, fallback directory, Product movement or copying. Dry verification is default. Apply requires the verified checkpoint checksum, rechecks current inputs and writes a canonical reused receipt in the existing recursive ledger. Its product_recovery audit retains the previous node, source Product identity, checksum and recovery time; no historical execution is forged. Other nodes, root outcomes and unresolved dependencies are unchanged. All new checks are local to this opt-in recovery operation; ordinary consumers are not tightened. Pending supersession and non-dependency failures are not recoverable through this operation. Apply is offline maintenance only and requires an explicit operator acknowledgement that all Trace writers are quiescent. The checksum and idle checks are not a cross-process lock or protection from an already loaded stale writer. Online recovery is not supported; this issue does not introduce global locks, CAS gates or changed admission for running Apps.
  • Issue #275 also provides opt-in recover_evidence_handoff for an admitted failed leaf explicitly declared with the generic evidence control role in an idle, open owning Cycle. This offline maintenance execution first verifies the canonical identity, lifecycle, checkpoint checksum and quiescence acknowledgement, then invokes only that leaf through the existing bounded graph/ToolLoop path. It does not call reset, prune downstream receipts, seed Products, or grant Product replacement rights. Ordinary invocation-local envelope write admission remains authoritative. The real result replaces only the target receipt; the old target and protected-node identities remain in recovery audit, and all other checkpoint facts are retained. Existing producers keep their historical input provenance; they are not claimed to have consumed the newly computed Evidence. Failure remains failure. This maintenance operation is not normal resume, does not close the whole owning graph, and cannot be used concurrently with other Trace writers. Public Evidence still executes Bundle/Digest/schema/Projection, so real external calls require separate current authorization. No App-specific recovery values or new gates on ordinary consumers are introduced. Read-only status exposes only the latest exact-phase maintenance audit through bounded evidence_recovery fields; previous failures never become canonical phase state or continuation authority through this audit channel.
  • An app may start an origin/source graph without asset_id. Trace-owned projection Products live permanently in docs/ai-skills/<product>/<trace_id>/cycle-000N/; they are not temporary unbound files waiting to be moved under the first asset.
  • Evidence facts, digest, source handoff and any App-declared immutable origin set are written into that Trace root as soon as each stage completes. Candidate adoption must not move, copy, relabel or remap those Products.
  • One App production activation returns exactly one produced_asset_id candidate. Before adopting it, Orchestrator must request a generic Projection candidate-adoption action bound to product + trace_id + cycle_no + asset_id and verify its complete receipt. Only then may it inject the identity into later candidate-specific steps, surface it through parent graphs, or use it for active promotion.
  • A complete adoption receipt must be a successful nexrur_projection_active envelope whose inner action, product, trace, cycle and asset identities match exactly; it must also carry already_applied: bool, an immutable exact source Cycle pointer, that Cycle's sha256-tree-v1 manifest, the candidate destination ref and that same destination in artifact_refs. A success label without these facts is an adoption failure. The canonical public action is adopt_trace_asset; Issues #262 and #264 record this runtime cutover from whole-Trace movement to immutable source pointers and multi-asset adoption.
  • An app production leaf cannot supply or forge this receipt. Only the Orchestrator-owned Projection call creates it. A parent graph may verify and consume the complete receipt returned by its nested Orchestrator child; it never accepts a leaf-owned asset_adoption field as authority.
  • At a completed product boundary, Orchestrator may request active promotion only when the latest Schema result explicitly carries validation_passed=true. Schema false/null already prevents graph completion through the generic failed-result path; active projection independently rejects it as a final fail-closed guard.
  • Schema owns mechanical validation and its core ledger only. When an explicit control_envelope.role=schema succeeds at a graph_role=subagent Product boundary, Orchestrator must pass the exact returned schema_envelope to the generic Projection trace writer as envelopes/schema.json. Projection must succeed and return the verified artifact ref before the Schema phase is considered durable or any downstream phase may run. Orchestrator does not infer this boundary from an App name, phase name, tool name, or file path.
  • Checkpoint and same-cycle restart may reuse a successful Schema receipt only while that explicit projected schema.json ref still resolves to a file inside the same app_root. A missing, escaped, or stale ref forces Schema to rerun; it must never be treated as completed and deferred to Active failure.
  • A bounded run whose selected range ends before the product graph's final declared step is not a completed product boundary. It may persist immutable trace Products, but Orchestrator must skip active promotion even when every selected step succeeded.
  • A Schema subject carries receipts only for the selected range plus formally reused dependencies. App schemas may declare the shapes of known phase_results, but must not statically require phases outside that bounded range. Orchestrator remains the single truth for range/dependency completeness; the Product schema remains strict about required business artifact_refs.
  • produced_asset_id may fill a Cycle without an adopted asset. If the current Cycle already has one, the returned value must equal that identity unless the accepted Campaign action explicitly carries asset_replacement_authorized=true. The Orchestrator validates that boolean but never derives it from or interprets transition_intent. A different value without the boolean authorization is an orchestrator contract failure. The replacement also requires a later canonical Cycle; the boolean cannot authorize two Assets in one Cycle. Multiple non-empty produced identities in one activation are always a conflict. Two production steps must never compete to adopt the active asset for one Cycle.
  • Asset birth and restart consume only the graph's standard identity fields: product, trace_id, cycle_no and optional asset_id. Absence of asset_id means the graph is unbound. A production leaf does not receive or authorize a graph-wide projection_identity; Orchestrator compiles that action-scoped object only when it invokes Projection.
  • Adoption failure leaves the Cycle without a candidate identity and failed; Orchestrator must not publish/adopt the candidate identity or attempt active promotion. An exact already-adopted receipt is idempotent success; a conflicting destination fails closed.
  • Adoption failure is attached as a post-Product execution incident so the producer candidate and its audit facts remain visible, but asset_id, canonical_id, _current_asset_id and graph-level produced_asset_id remain unset. Downstream validation is not invoked as a successful dependent phase and active promotion is skipped because the product graph is failed.
  • After verified adoption, Orchestrator keeps Trace-owned origin locators unchanged and uses the receipt's candidate destination only for candidate-scoped downstream Products. Product bytes, checksums and the adoption receipt remain untouched.
  • After adoption, the current Cycle checkpoint carries the standard active asset fields (asset_id, canonical_id, _current_asset_id). It does not persist a graph-wide projection_identity; that object is action-scoped and is compiled only at a callable boundary whose closed contract explicitly requires a Projection identity.
  • Orchestrator never infers unadopted-Trace archival from Cycle count or missing asset_id. It may request archive_unadopted_trace only when Campaign carries an explicit app-authorized terminal disposition. App policy owns all retry, budget and exhaustion thresholds.
  • A terminal logical repair block such as normalize resolves to its declared local executable entry such as normalize_evidence. Restart handoff dependency closure must use the same resolution; otherwise a precise nested restart could discard successful predecessor receipts.
  • Before a Campaign-approved next-cycle partial graph starts, Orchestrator may pass only generic artifact_refs from successful pre-restart receipts to Projection. product_ref, upstream_product_refs and other Product identity pointers are never copy sources: a Product that declares trace_id/cycle_no cannot be manufactured for another cycle by copying bytes.
  • Same-cycle exact phase resume never calls Projection seeding. It reuses only bounded successful receipts and existing same-cycle Product pointers. The target phase remains the only owner allowed to write or replace its canonical Product in that cycle. Every leaf invocation receives a substrate-compiled projection_write_admission: ordinary first execution uses create_or_verify. An attempted exact target resumed through same_cycle_phase_resume receives exact_phase_retry + replace, so it can repair a partial canonical Product left by its own failed attempt. An explicit completed-target same_cycle_phase_rerun receives exact_phase_rerun + replace. Both forms require the admission's Trace, Cycle and exact phase_path to match that leaf. The replacement bit is compiled only after resume() validates the canonical target lifecycle; a caller-supplied goal or _restart_scope cannot manufacture it.
  • Before an exact same-Cycle target runs, Orchestrator invalidates its canonical recursive receipt and every downstream receipt at each validated graph depth. Dependency receipts before the target remain reusable. This reset follows contract step_order and validated child graph identity; it never uses App names, string guesses, or mutates a sealed Cycle. Each removed downstream receipt creates one substrate-owned pending Product supersession right. When that exact downstream leaf next runs, its admission is upstream_rerun_supersession + replace; success consumes the right, while a failed attempt preserves it for retry. A next-Cycle reset clears the scope because the new Cycle already has a distinct Product path.
  • Projection is the final write gate. Product bundles and single AI-skill files default to create_or_verify; replace is accepted only with a Trace/Cycle- compatible Orchestrator admission for an exact retry, exact rerun, or pending downstream supersession. Standard envelopes are the current Cycle's canonical phase handoff and may advance under the separate invocation-local envelope_write_policy: replace, including ordinary nested-to-outer graph progression. That envelope authority cannot replace App-owned files. App callables may forward the admission but cannot broaden it or choose replace independently.
  • A parent-graph failure that occurs before the selected child graph starts may update the parent step status, but it has no authority to replace that child graph's restart snapshot. A result without the explicit child graph_path must preserve and retarget the latest same-trace/same-cycle identity-complete snapshot. A conflicting graph path fails closed before checkpoint mutation; an identity-incomplete handoff is never persisted or consumed.
  • A nested partial restart also carries bounded receipts for successful phases before the restart leaf. These receipts contain control shape such as status, phase and artifact refs, never the prior raw phase result. Generic recovery facts required by later substrate boundaries, including validation_passed and active_handoff_ref, remain in the receipt; referenced business content does not. Schema and later phases therefore see the complete logical phase chain without rerunning L1 or embedding old working memory in the checkpoint.
  • Receipt selection treats the orchestrator graph key as the canonical phase identity. A shared tool may report a generic internal phase such as step6, but that label must not hide graph nodes such as prepare_fail2pivot from the dependency handoff.
  • Unknown segments and non-terminal leaf steps fail with INVALID_RESTART_PHASE_PATH; they never fall back to a wider parent rerun.
  • A failure after the product Step6 boundary, such as active promotion I/O failure, invalidates that product graph's preceding no_failure_to_pivot handoff. Checkpoints must preserve the post-graph failure itself and must not reuse a stale successful Step6 diagnosis as the campaign decision.
  • Orchestrator may generate a trace id only when it is used outside Campaign control and no trace_id was supplied.
  • Orchestrator must generate a fresh step_run_id before every executable graph phase attempt. Leaf ToolLoop payloads must expose that id as both step_run_id and the runtime run_id; pipeline_run_id remains unchanged across the graph and is only a grouping id.

This prevents trace identity from being coupled to "one process invocation", "one recursive graph call" or "one asset". A Trace is one App-declared immutable origin/source lineage governed by Campaign and may span multiple continuation campaigns and sequential assets; a Cycle is one continuous execution attempt inside that same Trace.

6.1.1 One checkpoint state machine, three continuation modes

Trace resume and same-cycle exact Phase resume are not two independent state machines. There is one persisted checkpoint state machine. Exact Phase resume is one scoped execution mode inside that state machine; it must not reinterpret or replace the outer Trace/Cycle identity.

Full execution and bounded exact-Phase execution use this same state machine. Starting a new CLI process changes campaign_id; it does not create a second lifecycle model. The continuation modes are:

ModeIdentitySelectionOwner
Checkpoint recoverysame Trace, same Cyclecheckpoint current_step / next App stepOrchestrator
Exact Phase continuation inside an open owning Subagentsame Trace, same Cycleone validated recursive phase_path and leafOrchestrator
Campaign repair/resetsame Trace, next Cycle, or an explicitly authorized replacement TraceCampaign decides; Orchestrator materializesCampaign + Orchestrator

action_kind=same_cycle_phase_resume identifies the second mode only. It does not say whether the target leaf has run before. Target lifecycle is derived from the selected checkpoint's canonical recursive node and attempt receipt before any reset or metadata clearing; callers and Apps must not declare or guess it.

An exact leaf remains the requested execution boundary for both success and failure. A stopped Product does not authorize Orchestrator to enlarge that invocation into Schema, Step6 or Fail2Pivot. When the caller later starts an exact step6_evidence or diagnosis leaf, Orchestrator may reconstruct its input from the same checkpoint's bounded successful dependencies plus prior failed/blocked control receipts. Those diagnostic receipts are context, not completed dependencies, and they do not change the outcome of the earlier invocation. Cycle still open therefore never means the current worker may keep running. It means only that the canonical recursive ledger has not yet closed the target's owning app-level Subagent graph and no accepted restart has sealed the Cycle.

Subagent closure is mechanical and recursive. It requires an identity-complete terminal receipt for the declared app-level Subagent graph, with all required child graphs and contract-declared Product/Schema/Step6/post-graph closeout boundaries resolved. A bounded parent invocation returning completed, a compact Phase receipt, or a UI display state is not closure proof. Closure is derived from the checkpoint graph contract and recursive ledger only. Completion of a nested module or leaf does not by itself close its owning Subagent and therefore does not force a new Cycle.

The owning child-graph result persists graph_closure with version=1, an explicit role=subagent, closed, terminal step/exec identity, and a mechanical reason. Same-Cycle admission reads only this root child-graph receipt. It never promotes a descendant Module receipt or root display status into Subagent closure. For checkpoints written before graph_closure existed, a successful Active Promotion attached to an identity-complete child orchestrator result is the only accepted migration proof; every weaker legacy state remains open.

A failed terminal Phase closes the Subagent only when its bounded campaign_control.status is one of the Campaign-accepted terminal dispositions: restart_requested, halt, or escalation_requested. Merely carrying a campaign_control mapping is not closure proof. In particular, handoff_failed, diagnosis_unavailable, or any other unaccepted control state keeps the Subagent open for exact same-Cycle continuation. Admission cross-checks a persisted terminal_disposition=campaign_handoff receipt against the fixed terminal phase_receipts[terminal_exec_id]; a contradictory historical receipt is treated as open without mutating the checkpoint. A missing terminal compact receipt does not retroactively invalidate an otherwise valid older closure receipt.

The checkpoint has one recursive state ledger. Root StepState owns the App step and StepState.metadata.nested_steps owns its descendant nodes. Each descendant node records its explicit graph identity, execution status, admission fact and bounded attempt/result receipt. metadata.result is only the latest parent invocation result; it may not replace descendant state. _restart_scope is a pending execution command. _restart_handoff is a derived, invocation-local transport compiled from the canonical recursive ledger after path validation; same-cycle execution must never persist it back into checkpoint config or use it as a second lifecycle store.

A dependency accepted from a formal Product/adoption handoff is persisted in that same recursive ledger as status=completed, attempt_admitted=false and dependency_reused=true, with its bounded validated receipt and graph identity. This is not an execution attempt and cannot prove that the selected target ran; it only lets later same-cycle resumes reconstruct the same authorized dependency closure without retaining a second handoff state store.

One generic identity-preserving case is distinct: a dependency reused through same_trace_restart_handoff may preserve an earlier completion in the same Trace and the same Cycle. If its bounded result names that exact Trace/Cycle, retains status=completed, and passes the normal result gate, the canonical node projects as a completed exact target even though the current invocation truth remains attempt_admitted=false. It may therefore be explicitly rerun inside the still-open owning Subagent. Cross-Cycle reuse, parent-receipt coverage, missing identity or a failed/conflicting receipt never qualifies and remains fail closed.

Every exact target has exactly three lifecycle states:

Target stateMechanical meaningRequired behavior
not_startedNo admitted attempt exists for this exact graph path and leaf. A parent failure before child admission is still not_started.Reuse the complete successful same-cycle dependency closure, compile the normal first-run input, and execute the leaf.
attemptedThe exact leaf was admitted previously and the checkpoint contains its bounded durable attempt/input receipt.Replay the same durable input/operation identity. Missing, escaped, corrupted or conflicting durable input fails closed.
completedThe exact leaf has a dependency-compatible completed receipt in this checkpoint.Inside an open owning Subagent, ordinary resume is an idempotent no-op and explicit rerun may execute the leaf. Once the owning Subagent is closed, continuation projects a next-Cycle reset instead.

An explicit rerun of completed first snapshots its valid prerequisite handoff, then removes the target and all downstream canonical receipts. The rerun target receives exact current-Cycle Product replacement admission; every removed downstream receipt receives a separate pending supersession right for its own next execution. After the target completes, the next downstream Phase is not_started; a stale receipt from the earlier attempt cannot remain runnable or appear completed, and its stale Product can be replaced only once that Phase actually runs.

The public Trace status projection must expose this exact-target lifecycle beside each recursive Phase. A compact parent phase_receipts success may remain visible as historical execution/result status, but it never proves that the canonical recursive node is completed and never authorizes --rerun-completed-phase. The projection first expands every Phase declared by the validated recursive contract, then overlays canonical nested_steps and compact receipts. A declared Phase with no runtime record is therefore pending / not_started, not absent; its continuation is derived mechanically from the Cycle boundary and graph. When an older checkpoint has a compact success receipt but no nested_steps node, the exact target projects as not_started. That is only the leaf lifecycle, not action authorization. Consumers must also use the substrate-projected Cycle boundary and Phase continuation action; they may not turn not_started into a same-Cycle resume or derive a rerun flag from display status.

Every checkpoint Cycle durably records one cycle_execution_scope:

version: 1
entry_step: transform
entry_phase_path: [transform-agent, normalize]
transition: initial | campaign_restart | explicit_reset
transition_intent: string | null  # opaque App-owned authorization identity
asset_replacement_authorized: false  # explicit generic replacement gate
source_cycle_no: 1

transition is the substrate execution class. transition_intent is an optional App-owned semantic discriminator carried from an accepted Campaign route. The Orchestrator validates presence/equality where the route requires it and otherwise treats it as opaque; it must not derive it from target paths, asset changes or Product contents. asset_replacement_authorized is an independent boolean copied from the accepted Campaign route. It defaults to false, is never inferred from the opaque intent, and is consumed only by the generic Asset adoption guard.

The scope lives for the whole Cycle; unlike _restart_scope, it is not consumed after the first invocation. A target requires a next-Cycle reset when either:

  • its App root precedes entry_step;
  • its owning app-level Subagent graph is already mechanically closed; or
  • execution has entered a later App/Subagent and the target re-enters an earlier closed owner.

These are graph-order and recursive-closure rules, not App-name rules. Public Trace status projects such a target as operation=reset, cycle_transition=new_cycle; live resume() rejects the same request server-side. The explicit reset keeps the Trace identity, seals the source Cycle, increments cycle_no, and writes a new scope for the new Cycle. Thus a Trace may contain Cycle 1 full flow, Cycle 2 beginning at a later repair entry, and Cycle 3 re-entering an earlier graph at an exact validated Phase without mixing those execution ranges.

An explicit reset must name its exact source_cycle_no. The source must still be the Trace tip when the next checkpoint is created. Creation uses a no-replace atomic checkpoint publish: concurrent or replayed reset requests may produce one winner only, and every stale loser fails closed. The cycle_execution_scope is substrate-owned checkpoint identity; invocation payloads may repeat the identical value but may never replace it.

Cross-Cycle handoff is strictly adjacent. Cycle N+1 is compiled only from the sealed Cycle N checkpoint's authorized bounded receipts and immutable Product references. Those receipts may retain older provenance, but Orchestrator never skips the source Cycle, scans cycle-* for a latest Product, or copies Product bytes to manufacture current-Cycle truth.

A later exact Phase resume inside Cycle N+1 reuses that checkpoint; it does not create another Cross-Cycle handoff. If an upstream receipt already carries the governed Cycle N -> N+1 provenance, same-Cycle replay must preserve it verbatim rather than overwrite source_cycle(_no) with N+1. Rewriting the provenance would make one receipt claim both cross-Cycle and current-Cycle origin and must fail closed.

running is an execution status, not proof of admission. Payload compilation and all preflight gates occur before admission. A preflight failure records attempt_admitted=false and leaves the lifecycle not_started. Only an explicit admitted attempt may produce attempted. An admitted/running/failed target whose required durable attempt receipt is missing is checkpoint corruption, not a fourth lifecycle state and not not_started. Conversely, absence of a target receipt cannot by itself prove corruption: for a genuinely not_started target, no target receipt should exist. The explicit admission fact, node status and checkpoint attempt record must agree.

Exact continuation ordering is fixed:

load and validate the canonical Trace/Cycle checkpoint
  -> validate the complete recursive target path
  -> read the identity-complete recursive nodes and successful dependency closure
  -> derive target state before clearing any step metadata
  -> completed: return idempotently
  -> not_started: compile the ordinary first-run input
  -> attempted: validate and select the durable target-attempt input
  -> compile an invocation-local restart handoff
  -> atomically persist only the selected checkpoint state transition
  -> execute the target and bounded downstream range with the derived handoff

A parent-layer failure may update parent execution status, but it cannot create an attempted child, erase a child attempt, or replace the last identity-complete child node. Switching the exact target within the same Trace/Cycle selects a different node in the same recursive ledger; it never reconstructs lifecycle from the newest parent failure receipt or a prior handoff.

Non-admitted step_started / step_finished observations preserve any surviving admitted or formally reused recursive node, including its receipt, run identity and original timestamps. The latest scheduling observation is retained separately as bounded last_observation; it never authorizes continuation or becomes a replacement attempt. Explicit rerun/reset still invalidates the contract-scoped target/downstream nodes before execution, so this protection cannot resurrect an invalidated dependency. Public projection takes status, result and diagnostics from the same canonical recursive node rather than mixing a completed node with a later parent's blocked receipt. Regression tracking: nexrur#269. Trace status also carries the bounded opaque asset_id from the checkpoint config so App readers can validate an exact Product location; the substrate does not infer Product paths or interpret business identity from that value.

6.1.1 Read-only status and lineage index consistency

Decision: approved design and opt-in implementation, 2026-09-06; production activation and full end-to-end acceptance remain pending. Tracking: nexrur#278, consumer integration: WQ-YI#355.

Authorized implementation protocol (2026-09-06). The user has explicitly approved only lineage.sqlite3 and lineage.sqlite3-journal under the canonical Orchestrator root. Existing source files own all implementation. The additive --rebuild-lineage-index --exclusive-maintenance operation initializes or repairs the derived state; its --dry-run is read-only. Real existing history is not initialized or replayed by this coding authorization. Maintenance requires all old writers stopped; an acknowledgement is not a replacement for the namespace lock or coverage verification.

SQLite uses PERSIST rollback journals (truncated after commit), FULL synchronous writes and memory-only temporary tables: no WAL, SHM, additional lock file or background service. Namespace critical sections use a Windows Local named mutex (same Windows login session, including multiple VS Code processes) or an advisory lock on the existing directory on supported local POSIX filesystems. Locks cover only version/identity/admission checks and publication, never graph execution, provider calls or a whole Campaign. Readers never repair a hot journal.

The committed index contains the exact canonical inventory, content digests and mechanical Trace/Cycle/App/graph/replacement facts. File identity, size and precise timestamps invalidate coverage; they are not a substitute for the committed generation/content proof. Every indexed writer compares the actual source bytes against the version from which its update was computed, not a newly loaded version attached to an old result. Publication commits a bounded before/after digest intent, atomically publishes one checkpoint, then commits the derived row and clears the intent. Interruption leaves the intent visible. Explicit repair accepts only the recorded before or after state, rebuilds from canonical facts, and does not replay or rewrite historical outcomes.

Only explicit maintenance may discard an unreadable derived database after SQLite reports CORRUPT/NOTADB and canonical checkpoint validation has completed. It reports discarded_corrupt_index; unreadable prior intents cannot establish exactly-once recovery. Permission/busy errors, unknown protocols/namespaces and readable intents with an unexpected third checkpoint version remain failures. Database and journal must be local regular files, not symlink/hardlink aliases.

An uninitialized namespace keeps the existing storage/read contract. Once enabled, a stale/corrupt/incomplete index or pending intent cannot silently downgrade a writer to the unchecked path. Queries retain the authoritative read-only scan for history when the acceleration proof is unavailable, but report unavailable write authority (lineage_index_unavailable) instead of an executable action. This technical state does not change any recorded Phase outcome. Keeping the truncated journal also detects deletion of just the database. Removing both approved carriers, or in-place tampering that preserves every observable file-identity/timestamp field, is outside the supported writer protocol and requires exclusive maintenance; two files cannot prove a prior activation after both have been removed. There is no claim of arbitrary-filesystem-tamper detection, cross-Windows-login-session locking or distributed/NFS locking. All participating writers must run the new protocol before a namespace is activated; old code cannot be fenced by assumption.

This section changes the target cost and consistency of lifecycle inspection, not the App's lifecycle policy or the set of legally writable Trace/Cycle identities. Uninitialized consumers still perform the scan described below; initialized namespaces use the verified metadata path. No service is introduced.

Observed gap. Commit 6443f022 added successor discovery to status on 2026-08-30. _replacement_successor_trace_ids reads first-Cycle checkpoints across the canonical root and caches its index only inside the current process. Each fresh CLI loses that cache. Root-directory mtime also fails to observe a checkpoint arriving or being replaced inside an already existing Trace directory. _assert_checkpoint_writable is independently called by execution and live saves, but also shared that cache: another call was not proof of freshness. The bounded 2026-09-06 implementation removes this root-mtime cache, not the remaining scan.

Ownership and sources of truth. Orchestrator owns the derived relationship index, its completeness, invalidation, publication and execution-time checks. CLI owns a lightweight read-only entry; HTTP adapters own transport/request coalescing and display freshness, never relationship inference. Formal checkpoint identity and accepted config.previous_trace_id remain evidence; parent_trace_id does not seal a predecessor. No App name, business Phase, candidate or budget value belongs in this index algorithm.

The index is derived and rebuildable, not a second mutable checkpoint. Its logical records must prove the canonical source locator, accepted immutable Trace/App/graph identity, Cycle-1 relationship and relationship revision/digest. A published generation must also prove coverage of its checkpoint namespace; verifying a few positive entries alone cannot prove that no successor exists. Checkpoint content changes unrelated to immutable relationship identity must not be confused with a new relationship. These are internal storage invariants, not new required public payload fields.

Read path. Routine status must read the selected checkpoint and relevant verified relationship metadata, not all historical checkpoint bodies. An approved initialization or repair may perform one auditable full scan; ordinary --status must not secretly bootstrap, migrate, rebuild, write an index or call providers. Completeness is distinct from an empty successor set. Missing, corrupt, stale, partially published or unverifiable metadata cannot establish allowed=true. Such a read retains available historical facts and reports unavailable current authority; it must not fabricate a business failure, completed Phase or new run. Positive verified successor evidence may still establish read-only status.

Publication and concurrency. The implementation must satisfy all of these invariants before the fast path can become active:

  • Accepting a Trace, publishing its first checkpoint, replacing a Trace and advancing a Cycle participate in one recoverable publication protocol. A directory existing before its checkpoint is not a committed empty relation.
  • Identity validation, checking the currently writable predecessor/tip and publishing the transition have a cross-process serialization/fencing point. Two windows cannot both accept incompatible successors based on the same old observation; a stale running writer cannot save after its scope is sealed.
  • Every supported writer participates, including resume/reset, live checkpoint saves and maintenance. An in-process lock, root mtime, TTL, atomic replacement of only one file or an HTTP cache is insufficient for multi-file/multi-process publication. The exact lock, commit marker and crash-recovery mechanism must be reviewed before implementation, rather than presumed to exist today.
  • Readers recognize only a complete committed generation. Writer crashes, interrupted publication, repair/import and detected out-of-protocol changes invalidate the affected proof; they do not publish a transient empty index. Recovery verifies formal evidence and never rewrites historical outcomes.
  • A real mutation revalidates identity, latest Cycle, successor coverage and exact operation permission under the same freshness/publication protocol. A previously rendered continuation, revision or allowed=true cannot serve as authority. A cache hit is usable only with a proven current generation, not merely because _checkpoint_write_admission was called again.

Delivery and rollback boundary. Only the two carriers explicitly approved above may be created; a Campaign-local file is not the namespace index. Any additional storage, lock/commit artifact or service still requires separate user approval. The general no-new-files rule remains in force. Keep current public CLI/registry shapes and admitted business inputs unchanged. Do not make an uninitialized index a new mandatory field that disables existing Apps. Build and compare the derived state in an explicitly authorized preparation step, audit all writers/consumers, then enable the verified path. Until that is ready, record the current slow path as a known gap, not an implemented solution. Rollback may withdraw the optimization while retaining authoritative verification; it must not restore root-mtime-only authorization or remove historical read-only checks. WQ-YI and other existing CLI consumers, including AUDIT, need joint offline verification; an untested consumer is not declared unaffected.

Acceptance required after implementation.

  • Measure full cold CLI and complete HTTP responses separately from engine import/core-function timing, including I/O bytes, child-process count and contention. On the same reference host, an initialized normal path targets cold CLI at most 5 seconds and repeated HTTP p95 at most 2 seconds. These are engineering budgets, not execution watchdogs or new runtime rejection rules; initialization/rebuild is reported separately, never hidden in a query.
  • With unchanged requested scope and 10x unrelated historical payload volume, a routine status must not read every historical body. Include empty roots, missing checkpoints and multiple explicit App/graph/Cycle identities.
  • Exercise first checkpoint arriving after its directory, checkpoint replacement, next-Cycle creation, two competing replacements, a stale live writer, process restart, half-commit recovery and missing/corrupt/stale index generations. New valid work remains admitted; sealed history never regains write access.
  • Query and mutation use the same lifecycle decision rules. Shared HTTP reads never share write authority across callers/scopes. Raw historical checksums stay unchanged and read-only probes perform no network, credential or write operations. Real provider/production runs remain separately authorized.

First-stage historical implementation record (2026-09-06, before carrier authorization; superseded by the opt-in protocol above; #278 remains open). The existing package facades now load exports on demand, avoiding unrelated Campaign, ToolLoop and provider imports on status. Checkpoint projection still reuses pure definitions from execution.py; no graph execution is started by that import. Root-mtime-only lineage caching has been removed for both query and mutation callers. Focused tests cover first-checkpoint arrival and atomic replacement with unchanged ancestor mtime, parent-only non-sealing and a new Cycle sealing its predecessor; they do not prove cross-process serialization.

At that first stage persistent metadata and namespace publication/fencing were not implemented. The authoritative legacy scan remained a cost/consistency gap, not an accelerated index. In particular, Checkpoint.save has process-local path locking and single-file atomic replacement only; save_new prevents overwriting one target, not competing transitions to different targets. Campaign's direct next-Cycle save must join any future writer protocol too. No persistent carrier had yet been approved at that stage; neither HTTP coalescing nor the cache removal supplied that protocol. Full cold CLI/hot HTTP budgets and multi-process/crash acceptance remain separate required checks before closing this design item.

One read-only full CLI sample on 2026-09-06 against the existing WQ-YI Cycle 1 returned in 3.487 s (new Python process, filesystem cache not flushed), with 47 Phase projections and 101,902 UTF-8 output bytes. The child audit guard prohibited network, credential reads and file mutations; the selected checkpoint checksum was unchanged. No unrelated Campaign/ToolLoop/provider module loaded. However, it opened 271 distinct checkpoints (291 opens), confirming that the historical-body scan requirement is not met. This one sample is neither an HTTP p95 nor proof of scale invariance. Removing the unsafe cache can increase warm admission/live-save cost; the deferred index must address that without reintroducing stale write authority.

The same root's 271 Cycle-1 bodies total 429,543,195 bytes. Three additional warm scan samples took 2.452 / 2.531 / 2.801 s; in a separate 2.952 s sample, 19 full Checkpoint loads accounted for only 0.121 s. Avoiding object construction alone cannot resolve the dominant historical-body I/O. This is an explicit remaining live-save performance blocker, not a completed production rollout. At that stage carrier approval and publication/crash-recovery review were still required. The subsequent approval and implementation are recorded above; the real namespace still has not been initialized by this coding turn.

Earlier bounded create-only repair (2026-09-06, #278). First Trace acceptance and Campaign next-Cycle publication use the existing Checkpoint.save_new primitive, as explicit prepare_reset already does. An earlier existence check is not permission to overwrite another writer's intervening publication. A conflict must surface as a stale action before acceptance/restart success is reported; it must not silently allocate a different Cycle or overwrite the winner. An ordinary repeat acceptance still follows the existing identity and writable-tip checks and remains read-only/idempotent. The repair leaves App policies and the set of valid new targets unchanged.

That repair alone protected only two create-only entrypoints. The subsequently implemented opt-in protocol also covers run initial/live/final saves, reset, maintenance and Campaign transitions through existing orchestrator/contract.py. It is not confined to the read-only projection or locks around outer callers. The historical-body scan still applies to uninitialized namespaces.

Opt-in code delivery and isolated acceptance (2026-09-06). This increment changes thirteen existing files, with no new source files:

  • Production: src/nexrur/cli/nexrur.py, src/nexrur/engines/campaign.py, src/nexrur/engines/orchestrator/{contract,checkpoint,orchestrator}.py.
  • Tests: src/tests/cli/test_campaign_cli.py, src/tests/engines/{test_campaign_finalize,test_nested_restart_scope,test_orchestrator_checkpoint_atomic}.py.
  • Design: docs/{DESIGN,DESIGN_CLI,DESIGN_ENGINES_CAMPAIGN,DESIGN_ENGINES_ORCHESTRATOR}.md.

Verification covers 321 passing distinct cases across the CLI, checkpoint, nested execution and related Campaign files; four actual OS symlink tests are skipped for Windows privilege restrictions. CLI's 136-case full run was followed by a 39-case focused run including the added initialized Product-only Cycle-3 adoption case. That legal first admission remains distinct from advancing a Trace with existing canonical history; no Product semantics enter storage. Two actual processes exercise competing replacements/next Cycles and killed writers before/after publication. Isolated guards forbid credentials, network and real-history writes. These results do not prove power-loss durability, cross-login-session mutual exclusion or complete production HTTP latency.

Isolated read-cost samples on the same host (temporary fixtures, not real-history activation): 271 canonical first checkpoints totaling 429,543,008 bytes gave 20 warm admission calls p50 0.624 s / p95 0.874 s; three fresh admission-only Python processes gave wall p50 1.558 s / p95 1.589 s. Each query read only the selected 1,088-byte checkpoint and no unrelated checkpoint body. An uninitialized scan of that fixture had read 429,544,096 bytes in 9.232 s under concurrent load; this is not substituted for the earlier real-root baseline.

At a fixed 32-checkpoint scope, unrelated body volume grew from 49,319,980 to 492,886,456 bytes. Warm p50/p95 was 0.063/0.240 s before growth and 0.065/0.134 s after an explicit rebuild and quiet-host read-only retest; unrelated body reads remained zero. A first contended post-growth sample reached 17.755 s cold wall p95, so it was not treated as a latency acceptance pass. The quiet retest cold wall p50/p95 was 0.811/1.013 s. These fixed-scope results do not establish the full 271-checkpoint, 4.3-GB target or complete HTTP p95.

The real WQ-YI four-step contract graph also passed a fully guarded cold CLI --dry-run in 3.003 s, with the user Cycle-1 checkpoint checksum unchanged. This validates planning, not a business execution or a full status/HTTP budget. Real-history activation and complete HTTP measurement remain outstanding; issue #278 stays open. AUDIT contract joint acceptance was subsequently completed for the offline code-delivery scope described below.

Offline cross-App delivery acceptance (2026-09-12). Clean AUDIT main was fast-forwarded to 8712a04; its upstream contracts already include explicit graph identity and router budgets. No AUDIT code or runtime records were edited. All 53 AUDIT and 42 WQ registered callables loaded, and both recursive graphs projected successfully. AUDIT's shared contract suite passed 58 cases; the remaining substrate changed-test suite passed 699 cases with 7 skips. Both real CLI --dry-run entrypoints passed guards forbidding credential-file reads, network, subprocess creation and writes. Measured after CLI import in one process: AUDIT 2.547 s and WQ 1.609 s; these are not cold-CLI or HTTP p95 claims. This closes the contract-compatibility delivery check, not production activation, live business execution or the remaining #278 performance targets.

6.2 Unified Trace / Cycle / Run Ledger Rule

All append-only runtime and product ledgers use the same identity stack:

campaign_id -> trace_id -> cycle_no -> run_id

Canonical meanings:

  • campaign_id:一次 CLI campaign。
  • trace_id:一个 App 声明的 immutable origin/source lineage,可依次服务多个 asset。
  • cycle_no:同一 trace 内被 diagnosis/campaign 接受后的第 N 轮 attempt。
  • pipeline_run_id:同一 trace/cycle 下的整条 graph execution grouping id。
  • step_run_id:每一个实际执行动作的事件 ID,只写入 payload / JSON / JSONL, 不进入目录路径。

Canonical ledger shapes:

docs/ai-runs/.engines/.toolloops/<trace_id>/cycle-000N/events.jsonl
docs/ai-runs/.engines/.orchestrator/<trace_id>/cycle-000N/checkpoint.json
docs/ai-runs/.core/.schema/<trace_id>/cycle-000N/schema.json
docs/ai-runs/.core/.schema/<trace_id>/cycle-000N/subjects/<sha256>.json
docs/ai-runs/.core/.audit/<trace_id>/cycle-000N/audit.jsonl
docs/ai-skills/<product>/<trace_id>/cycle-000N/...             # Trace-owned origin/source Products
docs/ai-skills/<product>/<asset_id>/<trace_id>/cycle-000N/...  # candidate-owned Products

active/ is the only intentional exception. It is a mutable latest-active snapshot and must not be treated as a ledger:

docs/ai-skills/<product>/<asset_id>/active/...

Rules:

  • Every actual phase/tool/schema/projection/fail2pivot execution must have a step_run_id in records, but that ID is not a directory segment.
  • .engines、.core、ai-skills 不允许各自发明一套身份目录。
  • Existing trace_id/run_id or trace_id/cycle-000N/run_id layouts are migration debt, not final contract.
  • Human replay should be able to follow the same trace_id -> cycle path across runtime facts, core validation/audit, and business projection.

Live App-step checkpoint projection

Campaign may ask Orchestrator to accept a fresh trace before graph execution. Acceptance writes a created checkpoint containing the selected root App steps as pending. During execution, root step boundaries atomically update that same checkpoint to running, then completed or failed; child phase details remain inside the child result/ledger and are not promoted into a second App graph.

For operations consoles, the root step metadata may retain bounded live nested_steps, and the final compact root receipt retains bounded recursive phase_receipts. The public status projection flattens these as phase_steps with canonical phase_path, lifecycle timestamps, Diagnosis/Campaign summaries and step-level context snapshots. This is a read model over the one root App checkpoint; it is not an independently runnable child checkpoint.

When a governed same_trace_restart_handoff reuses a completed recursive parent through a compact receipt, independently absent descendant receipts are covered by that parent receipt. The read model projects coverage.kind=covered_by_parent_receipt and denies continuation for those descendants. It must not manufacture an execution receipt, mark the descendant as independently completed, or expose it as an ordinary runnable not_started Phase. A real descendant receipt or canonical live node always keeps its own lifecycle and takes precedence over parent coverage.

The public read-only checkpoint projection returns:

  • trace/campaign/pipeline/cycle identity and current root step;
  • one mechanical write_admission envelope naming the selected Cycle, Trace tip, read-only state and either historical_cycle_sealed or trace_superseded; every Phase continuation is intersected with this global admission;
  • one Trace-level trace_actions.new_trace envelope derived from that same admission and the mechanical graph entry. It binds the writable predecessor Trace/Cycle, operation=new_trace, trace_transition=replacement, target App step and first declared nested Phase. Clients must not invent this action from UI state. Live execution supplies the predecessor with --previous-trace; the predecessor is sealed only after the replacement Trace is accepted, while stale, historical or superseded sources fail closed;
  • root step_order and step_deps;
  • each root step's validated child orchestrator identity, internal order and internal dependencies, or its ToolLoop leaf identity;
  • bounded root step receipts and lifecycle state;
  • bounded nested phase lifecycle state and before / delta / after context snapshots for core / engines / aiskills / memory / golden;
  • bounded result, Diagnosis and Campaign-control summaries needed by a read-only operations view. Full Product bodies, provider responses, checkpoint filesystem paths and working memory remain excluded.

Graph discovery uses the same strict contract loader as execution. It may recursively project declared graph structure, but it must not invent artifact I/O, allowed-tools policy, or dependencies from visual adjacency.

6.3 Campaign Nested Restart Scope

Campaign owns the decision to restart; Orchestrator owns making that restart real against the recursive graph.

The canonical campaign handoff can carry two layers:

campaign_control:
  status: restart_requested
  target_step: reports
  target_subagent: reports
  restart_from_phase_1: true
  rerun_target:
    app_step: reports
    phase_path:
      - reports
      - penetration
    target_leaf: penetration_construct

Interpretation:

  • target_step is the app-level checkpoint entry.
  • rerun_target.phase_path is a path through nested graph steps declared in pipeline.step_orchestrators.
  • rerun_target.target_leaf is optional and names a leaf step in the final nested graph.

Orchestrator must not treat penetration or penetration_construct as app steps in this example. It must reset the app checkpoint from reports, then carry a restart scope into the recursive graph so the reports child graph starts at penetration and the penetration child graph can start at penetration_construct.

Required execution behavior:

  1. Reset the top-level checkpoint from the resolved app entry.
  2. Persist the nested restart scope on the checkpoint or resume payload so it is available during the next cycle.
  3. When recursive execution enters a child graph whose parent phase matches the next phase_path segment, apply start_step inside that child graph.
  4. When the final graph is reached and target_leaf is present, use it as the final graph start_step.
  5. Run downstream phases normally from the selected start point; do not skip dependencies by directly invoking the leaf tool.

There are two different controls and they must not be collapsed:

checkpoint-backed exact resume
  -> same trace / same cycle / same checkpoint
  -> validate the full phase path before mutation
  -> derive `not_started | attempted | completed` before mutation
  -> rewind the selected root/nested state in place only for `not_started` or
     valid `attempted`
  -> accumulate bounded successful receipts across exact resumes in that cycle
  -> keep parent failure status separate from the child recovery snapshot
  -> the newest receipt for a phase is authoritative; older receipts only fill
     missing phase keys and never cross a trace or cycle boundary
  -> never seed or copy Product files
  -> a first execution uses the normal first-run input contract
  -> a true retry reuses its durable target-attempt input; it never substitutes
     compact receipts for a previously observed input
  -> a completed target is an idempotent no-op

Campaign-approved repair or explicit reset
  -> same trace / next cycle
  -> create a new cycle checkpoint
  -> apply the Campaign/reset restart scope

For a Campaign-approved next-cycle repair, successful upstream execution receipts required by the selected leaf may be reused as follows:

reset_from_step
  -> snapshot successful results along rerun_target.phase_path
  -> persist checkpoint.config._restart_handoff with source cycle provenance

prepare_reset
  -> resolve an explicit source trace
  -> verify optional asset identity
  -> clear stale restart scope when no new nested scope is supplied
  -> persist a new cycle checkpoint without overwriting the source cycle

resume
  -> carry _restart_handoff into the selected execution payload

run_contract_graph
  -> hydrate only the target leaf's transitive dependency closure
  -> inject reused results into phase_results / graph_context aliases
  -> mark reused=true, source_trace_id, source_cycle
  -> execute the target leaf and its downstream phases only

The handoff is bounded execution context, not a second artifact store. It keeps only the successful phase's public status, artifact/evidence refs, compact summary and execution provenance. Full business payloads, nested phase results and other large private fields must never be copied into _restart_handoff. This compact envelope replaces the nested result metadata that reset_from_step clears while leaving business content in its projected artifacts. A handoff whose trace_id, phase_path, target_leaf, or graph contract does not match the resumed graph must not be reused. App leaves must not scan previous cycle directories or choose a "latest" result; checkpoint inheritance is an Orchestrator responsibility. A carried prior-cycle Product pointer is read-only dependency truth: it is valid only under a completed upstream root, with the same trace and a self-consistent cycle/artifact path. It cannot satisfy a reset or failed root and cannot be rewritten as a current- cycle Product.

The invocation-local same-cycle graph transport is frozen as:

graphs:
  - phase_path: [owning-app-step]
    graph_path: <validated-child-orchestrator-path>
    target_phase: <exact-leaf>
    target_state: not_started | attempted | completed  # derived, not authoritative
    phase_results: {}       # bounded successful upstream receipts only
    target_receipt: null    # present only for an admitted target attempt

This object is never a checkpoint truth source. It is compiled after validating the requested recursive contracts and discarded after the invocation. On a retry after process loss, Orchestrator compiles it again from the same canonical recursive checkpoint nodes.

phase_results and target_receipt are different facts. The first transports the target's successful dependency closure. The second proves that this exact target was admitted before and carries only the bounded replay facts required by that target, such as an input snapshot ref/hash or external operation identity. A missing target_receipt is valid only for not_started; it must not be interpreted without target_state. Raw Product bodies remain behind their same-cycle refs.

For an explicit control_envelope.role=schema target, the three states mean:

not_started
  -> compile the normal Schema subject from the canonical same-cycle graph input
  -> Schema persists the content-addressed subject snapshot
  -> returned ref + sha256 become the target attempt receipt

attempted
  -> require and reuse that exact subject ref + sha256
  -> never rebuild a replacement subject from compact checkpoint receipts

completed
  -> return idempotently without invoking Schema

An old or damaged checkpoint that proves Schema was attempted but cannot prove its subject snapshot fails closed. A checkpoint that proves Schema was never started follows the ordinary first-run path; it is not rejected merely because no prior Schema subject exists. This rule is role-driven and contains no App, Product or phase-name knowledge.

Invalid scope is a control failure, not a soft fallback:

  • the app entry does not match the first path segment;
  • a path segment is not declared in the current graph's pipeline.step_orchestrators;
  • target_leaf is missing from the final graph step_order;
  • the requested path would bypass a module orchestrator.

This rule is the substrate equivalent of:

target_step finds the app entry;
phase_path finds the nested module;
target_leaf optionally finds the module leaf.

Apps should keep tightly coupled modules in local orchestrators. They should not promote a module to app level merely to make campaign restart precise.

6.4 External Async Operation Continuation

Issue #288 adds optional production-batch journal mode (ToolLoop design). The same exact-leaf owner and write admission govern its first execution, retry, explicit rerun and upstream replacement. Capability inspection carries the mode as mechanical scope identity; missing legacy tracking does not create a new maintenance requirement for this opt-in. Status remains read-only; only execution can begin a generation. Other journal and async consumers remain unchanged.

Issue #280 adds a separate opt-in same-Cycle per-item journal, described in the ToolLoop design. The owning leaf execution boundary injects its exact App/graph/Phase/Trace/Cycle/run scope out-of-band. A payload cannot nominate its own journal owner or manufacture exact-retry permission. Same-Cycle continuation may inspect only that same item scope and reuse checksum-verified formal result refs; it does not scan prior Cycles, manufacture provider handles or change the earlier-Cycle async contract below. Completed items are never automatically sent again; admitted/unknown items require explicit reconciliation, not blind replay. Known failed items require the existing exact-target retry/rerun authority and a new owning run before another bounded claim. Before a previously unadmitted root begins, the engine prepares empty item scope manifests for its registered opted-in leaves, including leaves outside the current bounded invocation. This is the same initial manifest as ordinary ToolLoop uses, not an execution receipt or a completed request. Consequently, completing one non-journal Phase and starting a journal Phase in a later invocation is equivalent to running them in one invocation. Preparation is part of execution, never status, and never resets an existing scope. No extra checkpoint schema or App defaults are introduced. Capability inspection does not invoke tools/resolvers; an unresolvable optional callable does not become a new root-wide admission gate. An old root without this first-admission proof still requires explicit recovery; missing item history must not be reinterpreted as zero previous requests. First-admission proof is captured before exact-target reset, not from the reset root's cleared metadata. Manifest publication checks that original admitted checkpoint version under the existing namespace lock. Clearing observations or another writer changing the checkpoint cannot authorize a fresh item history. The previous root-only, invocation-local bootstrap implementation was insufficient for phase-by-phase use (WQ #357 / Nexrur #283). Its maintenance route is not a substitute for this normal execution path. No real Trace migration, deployment or provider invocation is part of this code task.

Issue #283 specifies recover_items, an explicit offline maintenance operation and an opt-in owning-run alternative described by ToolLoop. Status may offer item_recovery_confirmation_required for an exact same-Cycle leaf with a registered recovery resolver and missing legacy scope. This offer is conditional: normal dependency/ownership checks remain, and the App resolver validates the formal input again before any new item claim. Parent range execution remains blocked; confirmation binds one exact leaf, never a whole graph. Request-local confirmation permits reaching that leaf, not bypassing its input validation. The following offline protocol is retained for operator-led maintenance:

recover_items is an explicit offline maintenance operation for this missing-manifest case. It verifies the current idle writable checkpoint, exact registered opted-in leaf, graph identity, explicit formal input ref/hash, opaque operation key and acknowledgement that previous item outcomes are unknown. The read-only capability probe keeps ordinary registry structure validation but loads only the selected registered function to inspect its journal parameter; it neither builds all executors nor calls deadline/request resolvers. Actual graph execution retains all existing executor/deadline/allowlist checks. This bounds the new preflight cost without a cache or changing unrelated callable admission. Read-only verification reports the current checksum and maintenance_required; apply additionally requires that checksum and exclusive maintenance. It does not run/reset the Phase, change its outcome, invalidate descendants or grant Product replacement rights. A separate checkpoint audit preserves the previous node and unknown history, and binds a scoped manifest with an explicit total new-claim cap. Pending audit/manifest publication can only be finished by the identical request; ordinary execution cannot consume partial admission or silently refill allowance. The existing namespace lock covers short local publication only, never external work. Uninitialized old writers still require the operator's quiescent window; this is not a claim of new global fencing. Already initialized scopes and tools that do not opt into the journal keep their existing execution contract. Without a maintenance audit, status recognizes a legacy maintenance failure only from exact structured error fields of a failed/blocked observation; matching text in source material, input, context or arbitrary metadata is not execution evidence.

The existing supersession scope retains producer provenance per pending target when a downstream exact restart records its own invalidations. An optional pending_origins mapping preserves that provenance; existing scopes use their recorded origin_phase_path. Restarting a pending target must not turn the target itself into proof of its upstream input.

Read-only status and exact-leaf readiness recognize the bounded formal-input succession protocol in ToolLoop #280. A successful upstream rerun must not leave a downstream leaf permanently blocked on the old input checksum. The existing pending replacement scope and successful origin artifact receipt are the authority, not a payload flag or a cleared node. Status, CLI admission and owning execution share the same proof; only execution appends the new input binding, preserving history and the original cumulative allowance. No App names or business rules are added.

Read-only status associates existing ToolLoop progress with both root steps and canonical nested phase_steps by their exact current step_run_id. Each entry may expose the same bounded progress shape. Events from another run, Cycle, Trace or absent Phase never fill a current entry. This projection grants no execution permission: the existing Phase continuation and current write admission remain the only action interface. The HTTP adapter translates opaque labels and sanitizes facts; the substrate has no App-specific progress fields.

Some leaf tools submit an external asynchronous operation and receive an opaque provider handle before a terminal result exists. If execution stops because the provider operation is still pending, a later cycle must be able to continue polling the same operation without submitting a duplicate request.

This is an Orchestrator restart-handoff responsibility, not ToolLoop history discovery. Campaign decides whether the next cycle is an operation continuation or a business repair/replan. Orchestrator then either carries one explicit, bounded progress handoff or carries none.

Campaign expresses continuation explicitly; Orchestrator must not infer it from a reason-code name:

campaign_control:
  action_kind: resume_existing_operation
  rerun_target:
    app_step: owning-subagent
    phase_path: [owning-subagent]
    target_leaf: external_async_leaf
external operation still pending
  -> campaign requests continuation of the same executable leaf
  -> orchestrator selects the exact source trace/cycle
  -> orchestrator copies bounded matching tool progress into _restart_handoff
  -> ToolLoop injects that explicit handoff into the callable
  -> callable validates request identity and polls the existing provider handle

business repair/replan
  -> campaign restarts the selected repair boundary
  -> orchestrator does not carry prior async progress
  -> callable builds the repaired request and may submit a new operation

Canonical bounded shape:

_restart_handoff:
  async_operation:
    mode: continue_existing_operation
    source_trace_id: trace-...
    source_cycle_no: 1
    target_phase_path: [owning-subagent]
    target_leaf: external_async_leaf
    caller: batch:external_async_tool
    tool_name: external_async_tool
    request_identity: opaque-deterministic-id
    progress_events:
      - message: async_job_lineage
        run_id: step-...
        source_cycle_id: cycle-0001
        source_cycle_no: 1
        facts: {}  # bounded job handle and request-lineage facts only

Rules:

  • mode must be explicit. Absence means no cross-cycle operation resume.
  • The source trace must equal the current trace; replacement traces never reuse provider handles from the replaced asset attempt.
  • Orchestrator resolves aliases and logical blocks against the nested graph and writes one canonical executable target_phase_path/target_leaf. Raw route logical names such as normalize must not leak into ToolLoop path comparison after they resolve to an executable leaf such as normalize_evidence.
  • The target graph/leaf, caller and registered tool must match the resumed execution exactly before the handoff leaves Orchestrator.
  • async_operation is visible only while Orchestrator descends along its declared target_phase_path, and only the exact target leaf receives it as a ToolLoop argument. Downstream schema, Step6, fail2pivot and unrelated leaves may retain the ordinary bounded restart handoff, but must not receive the async continuation packet or be required to implement cooperative-async parameters.
  • Only unresolved operation progress is eligible. A terminal/quorum-complete operation must not be handed to a quality-repair cycle.
  • progress_events are bounded operational facts, not provider responses, expressions, prompts, metrics, diagnosis or business artifacts.
  • A normalized progress event is limited to 16 KiB and the complete event list to 96 KiB. The list retains the latest event for every distinct durable operation handle, then fills the remaining budget with the newest events. If the required handles alone exceed the budget, continuation fails closed.
  • Malformed JSONL records and records with invalid numeric identity are ignored individually. They must not invalidate a later well-formed event from the exact source trace/cycle/run stream.
  • Every resumable progress stream must carry one deterministic request_identity derived by the app callable from the actual external request. Trace, leaf, caller and tool identity are provenance, not sufficient request identity. Orchestrator preserves and checks consistency of the declared identity; it must not invent one from trace_id + leaf.
  • Orchestrator selects and validates source-cycle provenance, canonical target, caller/tool identity and bounded event shape. ToolLoop receives the normalized packet and does not reopen restart scope or resolve graph paths. The callable still validates request_identity against its current request before using a provider handle.
  • Every continuation reset rebuilds the handoff from the checkpoint cycle that just failed. A matching scope must not reuse the previous handoff because the latest cycle may have resumed old handles and submitted additional jobs.
  • The app-derived operation identity stays stable while the request stays the same; source cycle and source run provenance change on every refreshed handoff but do not change that operation identity.
  • A mismatch fails or ignores the continuation handoff according to the orchestrator contract; it must never fall back to scanning cycle-* paths.

Hard timeout boundary:

  • ToolLoopHardTimeout does not prove that the worker or provider operation was cancelled. Orchestrator must not convert it into continuation merely because the target leaf is resumable.
  • A continuation handoff is legal only when a matching handle and request identity were durably written before the failed cycle closed.
  • If the timeout occurred before durable identity/handle publication, operation state is unknown. The cycle fails closed and must not resubmit automatically.

The current uncommitted design that lets ToolLoop search every prior cycle in a trace is intentionally non-canonical. Its operational goal is preserved here, but cycle ownership moves to Orchestrator.


7. Step Payload Contract and Runtime Audit

Orchestrator is the only component that knows the current app/subagent/module graph shape at execution time. It maintains the complete graph context in its internal step payload. A leaf does not automatically receive that whole mapping: when its ToolLoop args_schema is a closed object (additionalProperties: false), Orchestrator mechanically projects only the declared top-level properties before deadline resolution and execution. The ToolLoop schema then validates the projected mapping. This projection knows no App fields or tool names; it is driven only by the declared schema. Open or empty schemas retain the complete payload for generic legacy callables.

Therefore a closed leaf cannot recover omitted values from _phase_results, directory names, or adjacent aliases. Required values must be explicitly declared by its args_schema and present in the graph payload. Missing values fail schema validation before the callable runs.

Required internal graph payload block:

orchestrator:
  contract_path: string
  parent_contract_path: string | null
  phase: string
  graph_role: app | subagent | module
  graph_product: string
  step_order: []
  full_step_order: []
  step_deps: {}
  step_orchestrators: {}
  diagnosis_phases: []  # derived by substrate; app contracts should not declare it
parent_orchestrator: {}  # present in nested graphs; previous orchestrator block before child override

Graph identity is App-authored contract data, not a substrate path convention. Every active orchestrator.yml must declare:

pipeline:
  graph_identity:
    role: app | subagent | module
    owner: opaque-non-empty-id
    product: opaque-product-id-or-null

owner is transported as graph_product; product is transported as projection_product. Product/module graphs require product; the app root may omit it because its child App step is mapped by the explicit projection contract. The runtime must not inspect .gemini, skills, or any directory-name convention, modules, directory names, or filename prefixes to infer any of these values. Missing or conflicting graph identity fails closed before leaf execution.

The projection contract must likewise arrive through an explicit projection_config or projection_config_path. Orchestrator must not construct an App-specific default path. If a graph requests bind, trace projection, partial-cycle seeding, or active promotion without that explicit contract, the operation fails closed.

Required adjacent internal graph payload fields:

campaign_id: string
asset_id: string
_current_asset_id: string
trace_id: string
cycle_no: integer
cycle_segment: string
current_step: string       # app-level step that owns the current recursive leaf
phase_path: []             # path from current_step through nested graph owners
phase: string
step: string
step_results: {}
_phase_results: {}
artifact_refs: {}
failed_phase: string | null
_failed_phase: string | null

Internal upstream convenience fields (not implicit public leaf inputs):

evidence_result: {}          # latest substrate evidence / obligations handoff
obligations_result: {}       # alias for the same evidence handoff
production_result: {}        # latest result whose control_envelope.role is production
schema_result: {}            # latest schema result
failed_phase_result: {}      # result for failed_phase, only when present

Execution-coordinate ownership is strict:

  • Orchestrator derives current_step from the app graph frame that owns the recursive execution. It is never copied from the current leaf tool id.
  • phase_path preserves the declared nested graph path beneath that app step.
  • phase and failed_phase preserve local leaf identity; they do not replace current_step.
  • Every nested child and Step6/fail2pivot payload must carry the same canonical app-level current_step while updating only its local phase facts.
  • For a step6_evidence leaf, Orchestrator must compile the diagnosis source coordinate explicitly as target_step: use the current graph's failed_phase when present, otherwise its latest canonical production_phase. Evidence then exposes the same value as current_scope.target_step.
  • Evidence assembly state is compiled from the leaf's explicit generic control_envelope.role, never from its tool id: evidence compiles to evidence, while step6_evidence compiles to step6_evidence. An explicit restart_evidence is preserved only on an evidence role. Unsupported or contradictory state fields fail before the callable runs, so an App may use opaque local aliases without teaching Orchestrator App names.
  • Diagnosis assets, phase_patterns, reason codes, error strings and artifact paths are not graph resolvers. Orchestrator must not consult them to invent or repair execution coordinates.
  • A missing or contradictory graph coordinate is an orchestrator contract failure. It must fail before Diagnosis rather than ask diagnosis/context.py to guess the owning app step.

Projection product identity follows the same explicit coordinate chain:

  • the root App step is the default product identity when it matches a declared products.<name> key in the app-owned Projection contract;
  • when the names differ, products.<name>.app_step must bind them explicitly;
  • a direct subagent/module graph call without a root App step must carry an explicit projection_product request field;
  • for that direct subagent/module call, Orchestrator resolves the declared product through products.<product>.app_step and uses the resulting App step as current_step and the leaf phase_path prefix. An omitted app_step retains the existing same-name contract default; explicit null/empty bindings cannot supply a direct-root App coordinate. Conflicting request coordinates or ambiguous bindings fail before the leaf. Product and App step identities must not be equated merely because one App uses the same names;
  • an existing direct graph that does not use a Projection contract remains supported: an explicit valid current_step supplies its App coordinate, otherwise the declared Product retains the same-name default. This mapping repair does not introduce a new mandatory Projection/startup gate;
  • nested module graphs inherit the owning App step's projection product while keeping their own module-level graph_product;
  • an explicit product, App step mapping, and Projection declaration must agree. Missing, undeclared, ambiguous, or conflicting identity fails closed at the first projection/bind boundary;
  • skill directory names and app-specific naming conventions are never product identity sources.

Rejected implementation: passing a local leaf such as normalize_fail2pivot as non-empty current_step, then relying on Diagnosis to map it back through app phase_patterns. That creates a second graph resolver outside Orchestrator and masks the actual producer defect. No compatibility fallback for this shape is permitted.

These fields are assembled from _phase_results for open generic leaves and internal graph work. A closed leaf receives one only when its args_schema names it explicitly. _phase_results, <phase>_result, workspace, top-level identity aliases, and other graph-local transport are not part of the fail2pivot contract and must not cross that closed boundary.

The closed fail2pivot leaf declares app_root, projection_identity, exactly one of step6_evidence / step6_evidence_ref, and its optional generic Diagnosis/MCP configuration. projection_identity.scope=asset carries asset_id; scope=trace forbids it. Orchestrator transports this identity unchanged when the caller supplies it. When the Step6-to-Diagnosis chain has no explicit identity, Orchestrator compiles it only from the current graph's declared Projection product plus canonical trace, cycle and bound asset coordinates. It must not consult errors, artifact paths or App naming conventions. StepSix Projection therefore writes either the bound asset trace or the unbound Product trace without guessing a placeholder asset.

The evidence obligations leaf uses this payload contract while internally running bundle/digest assembly. Its aiskills_read_plan is not ai-skills truth and is not a reader guess; it is an execution plan derived from Orchestrator graph context, current evidence state, restart/campaign handoff metadata, and projection identity.

Minimum plan shape:

aiskills_read_plan:
  state: evidence | step6_evidence | restart_evidence
  upstream: []
  current: {}
  diagnosis: {}
  reference: []
  warnings: []
  errors: []

Rules:

  • Orchestrator provides graph context. It does not classify aiskills files.
  • Nested graph payloads must preserve parent_orchestrator so evidence assembly can distinguish direct upstream active truth from ancestor/reference material.
  • build_evidence_bundle.py builds aiskills_read_plan from payload context. It does not read ai-skills files and does not digest content.
  • aiskills_read_plan.upstream contains only direct upstream active refs derived from parent_orchestrator.step_deps[parent_orchestrator.phase]. Local phase trace refs and ancestor refs must go to reference, not upstream.
  • If a direct upstream phase is outside the selected run slice, its active refs must be supplied explicitly by the App step or Projection contract. build_evidence_bundle.py does not open projection layouts or hydrate a missing active_manifest.json; the reader reports a missing required source according to the App source contract.
  • read_aiskills_artifacts.py executes aiskills_read_plan. It must not scan all active directories to infer upstream / diagnosis / reference.
  • build_evidence_digest.py materializes the aiskills lanes: aiskills/upstream/, restart-only aiskills/diagnosis/step6.json, and aiskills/reference.json.

Audit boundary:

  • Leaf ToolLoop call args and results, including aiskills_read_plan, are runtime evidence about execution and must be auditable under:
docs/ai-runs/.engines/.toolloops/**/*.jsonl

Checkpoint boundary:

  • checkpoint stores checkpoint_payload_mode: compact_receipt per app step;

  • step.metadata.result records only the current parent invocation; step.metadata.nested_steps is the sole resumable descendant-graph truth;

  • exact same-cycle continuation never clears the selected root step's canonical nested_steps; it resets only root invocation state while retaining recursive node identities, admission facts and bounded receipts;

  • a parent pre-child failure without a child admission cannot modify the child node;

  • Orchestrator derives the requested target's successful dependency closure from canonical sibling nodes and injects it through an invocation-local _restart_handoff; changing the target recompiles this transport and does not mutate or truncate the recursive ledger;

  • receipt keeps status/reason, graph range, artifact refs, campaign control, diagnosis verdict/failure facts, restart path, active promotion and cycle seed status, generic validation/handoff facts (validation_passed, active_handoff_ref), plus bounded summary and direct-child phase receipts required by partial restart; summary may carry pointer identity but never the referenced business payload;

  • each executed phase receives a bounded context_snapshot: before is the latest known five-bucket state, delta records execution status and produced artifact refs, and after is either a newly materialized evidence digest or an explicit inherited view (recomposed=false); missing buckets remain visible as unavailable rather than being fabricated;

  • receipt never embeds phase_results, evidence/production bodies, prompts, provider raw responses or field reports;

  • full products remain in trace/cycle artifacts and are reached through refs;

  • resume and campaign handoff must be reconstructable from the compact receipt.

  • Orchestrator graph checkpoints / traces are runtime evidence under:

docs/ai-runs/.engines/.orchestrator/**/*.json

There must be no active .engines/.evidence directory. If that path appears, it is old or invalid runtime layout.


8. Result Envelope

Product results exposed to a parent graph use one producer-owned envelope:

status: success | partial | failed
phase: <step_name>
artifact_refs: {}
warnings: []
errors: []
reason: ""
reason_code: ""

Product status has exactly three values:

success | partial | failed

A legal business skip, no-op, no-target, or confirmed-no-data outcome is not a fourth Product status. The app Production owner must return it as partial and must retain its original producer reason / reason_code.

Orchestrator is an executor and transport layer, not a Product status authority. It consumes and forwards the producer result as returned. It must not convert skipped into partial or failed, invent a replacement reason, or infer a Product status from artifacts, schema, Diagnosis, Step6, or Campaign output.

status=skipped is an invalid Product result, but that invalidity does not grant Orchestrator permission to replace the returned status, reason, or reason_code. Production contracts, schemas, and producer tests must prevent a leaf from emitting it. If an invalid receipt nevertheless reaches runtime, Orchestrator must retain that receipt unchanged in the phase results. Any graph execution or contract error must be recorded separately from the Product receipt; it must never be presented as a new Product result.

Whether a Product result satisfies graph dependencies is explicit in control_envelope.graph.disposition. The App ToolLoop declaration normally maps Product success | partial to continue and failed to stop, but that mapping is App contract truth, not an Orchestrator hard-coded status predicate. Normal execution, same-trace restart handoff, and checkpoint resume all consume the same compiled disposition. No artifact or Step6 gate may override it.

For Product dependencies, the intended three-state behavior is:

completed-compatible: success | partial
terminal failure:      failed
invalid Product status: skipped or any undeclared value; never rewritten

Runtime checkpoint lifecycle values such as pending / running / completed / failed are stored in checkpoint fields and are not Product status aliases.

Child graph results are wrapped as the parent step result through the existing normalized envelope and bounded receipt. The parent may add its own execution coordinates, but it must preserve every child/producer-owned field already present in that envelope, including status, reason, reason_code, failure_kind, errors, warnings, artifact refs, and evidence refs. It must not rename, summarize, compress, replace, or reinterpret those fields, and it must not select a later Step6 result as a replacement child outcome. Parent graphs do not inspect child internals except through that existing normalized envelope, bounded receipt, and declared refs.

Step6, Diagnosis, and Campaign results remain their own existing phase/control facts. They may explain a Product issue or request a later action, but they do not edit the current-cycle child result. A Product status may change only when a later Campaign cycle reruns the owning Production step and that producer returns a new result.

Only when a Production leaf crashes, times out, raises, or otherwise returns no Product receipt may Orchestrator create a substrate-owned runtime failure in the execution channel. If a producer returned a receipt, Orchestrator must preserve it. Runtime failure facts must remain distinguishable from a producer-returned business result and must not be presented as a reason chosen by Production.

This transport rule does not introduce a second Product collection or proof surface. In particular, Orchestrator must not add a parallel product_results bus, compare raw Production bodies with pointer/receipt envelopes, or require Evidence, Fail2Pivot, Diagnosis, or Campaign to echo a Product result.

8.1 Post-Product execution incidents

The seal point is the successful return of a producer-owned Product envelope to Orchestrator. After that return, Orchestrator must keep two facts separate:

product_result     = the exact producer-owned business result
execution_incident = a later substrate/runtime operation that did not complete

This is a field-ownership rule inside the existing result/checkpoint/trace surfaces. It does not create a new Product envelope, receipt, proof hash, file, or transport bus.

Post-Product failures include, without being limited to, produced asset identity conflicts, evidence-handoff/projection failures, and active-promotion failures. For any such failure Orchestrator must:

  1. retain the producer result, including its original status, reason, reason_code, failure_kind, refs, warnings, errors, and business fields;
  2. record the later failure as execution status/code/errors or a bounded runtime incident in the existing execution channel;
  3. stop or block the graph when the execution contract requires it; and
  4. expose the unchanged Product plus the separate execution incident to parent, checkpoint, Re-evidence, Diagnosis, and Campaign consumers.

It must not replace the Product dictionary with an orchestrator-authored failed Product, mutate the Product's producer-owned fields in place, or present a runtime code as the producer's reason. If the business result must change, Campaign must start a new cycle and rerun the owning Production step; only that producer may return the replacement Product.

For a failed child graph, that normalized envelope must surface a bounded failure receipt: reason_code, failure_kind, compact errors, diagnosis readiness and any child fail2pivot campaign control. It must not collapse a known child reason to generic phase_failed.

Provider-neutral runtime facts needed by diagnosis are part of that bounded receipt. Simulation-like leaves may contribute simulation_status, runtime_label, error_type, runtime_facts, metrics, quality_gate, and a bounded generic diagnosis_context. The latter may contain compact hard-failure facts such as error distributions, selected-field lineage, and provider-neutral terminal status. Full result collections and provider responses remain excluded. Orchestrator transports these facts but does not map them to an app diagnosis reason or campaign route.

App-owned diagnostic facts enter through one explicit generic control envelope:

leaf result.control_envelope.diagnosis.facts
  -> orchestrator phase_failure_facts.diagnosis_facts

The App ToolLoop declaration explicitly identifies the producer source field compiled into diagnosis.facts; additional admitted diagnostic source fields are compiled into diagnosis.fields. Orchestrator never probes the raw result for likely diagnostic names. diagnosis.facts must be a mapping. Orchestrator copies arbitrary role names without enumerating or interpreting them, applies the same scalar/depth/item limits as other control facts, and enforces a hard serialized container budget. An oversized role becomes a shape index; its full body must remain behind the leaf's normal artifact_refs / evidence_refs pointer channels. A non-mapping container is omitted and recorded as a generic leaf-contract violation. The substrate must not inspect business paths, compare numeric values, or infer a reason from any role inside this container.

A diagnosis-ready leaf may also publish the generic one-shot learning fields product_revision_id, upstream_contract_snapshot, and attempted_candidate_trial_keys. Orchestrator transports these fields into the same bounded phase_failure_facts receipt. It does not derive a revision from an artifact path, interpret the snapshot, construct a candidate identity, or decide whether a trial is eligible. Missing fields remain missing and are handled by the downstream Trial gate fail-closed.

Bounded means a hard serialized limit, not only a depth/key limit. Control strings are capped at 2,000 characters, each structured control field at 16 KiB, and the complete phase_failure_facts receipt at 64 KiB. Oversized structured fields become index summaries with counts/keys; raw payloads are never inlined.

A diagnosis-ready failed leaf must emit its own stable reason_code and failure_kind. Orchestrator builds the single bounded phase_failure_facts receipt from those explicit fields; it never promotes error text, reason-name suffixes, or app-specific codes into a reason/failure classification. Missing reason_code is an orchestrator contract failure. Current-cycle facts replace any top-level stale facts; prior-cycle diagnosis remains under upstream_feedback.


9. Failure and Diagnosis Semantics

Orchestrator is deterministic. It does not diagnose root cause and does not choose campaign routes.

Rules:

  • If a non-diagnosis leaf fails and a future implicit diagnosis step exists, downstream dependencies may become blocked, but the runner should continue far enough to invoke the diagnosis/fail2pivot step.
  • If no future diagnosis phase exists, unresolved required dependencies stop the graph with a failed envelope.
  • If a failed leaf or nested child has returned fail2pivot campaign control, its current graph stops and propagates that control receipt. It must not continue into a later block's Step6/fail2pivot and overwrite the first failure diagnosis.
  • nexrur_core_schema is a mechanical validation tool.
  • nexrur_fail2pivot is the Step6 diagnosis/campaign handoff tool and is recognized implicitly by the substrate orchestrator. App contracts should put it in step_order, not declare diagnosis_phases.
  • nexrur_fail2pivot writes Step6 trace artifacts and returns campaign control; it does not promote active.
  • nexrur_projection_active is not an orchestrator graph phase. Recursive orchestrator invokes it only after a complete subagent product graph succeeds.

Campaign decides replay/restart/halt/escalation. Diagnosis explains failure. Orchestrator only records what executed and why a graph step could or could not run.


10. Relationship to ToolLoopRunner

Orchestrator and ToolLoopRunner are separate but connected:

orchestrator/execution.py
  -> schedules graph step
  -> resolves leaf tool id
  -> asks ToolLoopRunner/callable executor to run that tool

toolloop.py
  -> loads tool specs
  -> loads callables
  -> enforces allowed tools, timeout, audit/evidence records
  -> returns tool result

ToolLoopRunner does not decide app/subagent/module order. Orchestrator does not own individual provider clients or app production semantics.

LLM and MCP are just registered ToolLoop tools from Orchestrator's point of view.


11. Relationship to Schema / Projection / Diagnosis / Campaign

These substrate capabilities are exposed as leaf tools:

nexrur_core_schema
nexrur_projection_trace
nexrur_fail2pivot
nexrur_llm_generate
nexrur_mcp_call

Orchestrator does not special-case their internals. It only resolves and runs their tool ids.

Boundary:

  • Schema validates mechanically.
  • Projection trace writes trace/cycle outputs.
  • Projection lifecycle adopts an app-authorized asset for one Trace/Cycle without moving Trace-owned origin Products, or archives it after an app-authorized terminal disposition. It does not own the threshold or business decision.
  • Projection active promotes active snapshots only after a complete subagent product graph succeeds; intermediate module Step6 and failed graphs never invoke it. It is not scheduled as a standalone graph step.
  • Fail2Pivot combines diagnosis and campaign control handoff.
  • Campaign owns retry/restart/halt lifecycle policy.
  • LLM/MCP providers are owned by their packages.

12. Naming Rules

Names must reveal layer:

mirror          = nested module phase in orchestrator.yml
mirror_mirror   = mirror module primary ToolLoop tool id
mirror_pdf_*    = mirror module extractor ToolLoop tool id

Hard rule:

A name in step_orchestrators is a graph step, not a tool. A leaf step not in step_orchestrators must be a registered tool id.

Do not reuse the same name for both a nested graph step and a ToolLoop tool id.


13. Runtime-Active Runner

src/nexrur/engines/orchestrator/execution.py owns the active graph runner, re-exported from nexrur.engines.orchestrator:

run_contract_graph(
  orchestrator_path,
  toolloop_path,
  payload,
  *,
  app_root,
  context
)

This runner:

  1. load the current orchestrator contract;
  2. resolve child paths from step_orchestrators;
  3. build a global ToolLoop registry from toolloop.yml;
  4. execute nested graphs recursively;
  5. execute leaf tool ids through ToolLoopRunner;
  6. return one normalized graph result.

The old handler-based run_phases_from_contract(...) API is retired. There is no active compatibility path where callers provide Python handler maps for phase execution. A phase is either:

  • a nested graph step declared in pipeline.step_orchestrators; or
  • a leaf tool id registered in app toolloop.yml.

If neither is true, Orchestrator returns a clear unresolved-step failure.


14. Hard-Cut Contract Requirements

  1. Keep app/subagent/module contracts minimal and human-auditable.
  2. Ensure every leaf step is a registered ToolLoop tool id.
  3. Ensure step_orchestrators only points to child orchestrator.yml files.
  4. Add duplicate tool id validation to ToolLoop registry loading.
  5. Wire CLI/campaign to call run_contract_graph(...).
  6. Keep retired skill*.py dispatch chains out of the active runtime path.
  7. Keep app production semantics inside registered tools, not inside Orchestrator.

15. Anti-patterns

  • orchestrator.yml contains callable.
  • orchestrator.yml contains callable ownership outside required pipeline.graph_identity.owner.
  • orchestrator.yml contains step_tools.
  • orchestrator.yml contains schema/campaign/diagnosis truth.
  • A leaf step does not resolve to a registered ToolLoop tool.
  • A nested module phase and a ToolLoop tool share the same id.
  • skill*.py owns the graph order after the recursive runner exists.
  • Orchestrator imports app scripts/toolloops/*.py directly.
  • ToolLoopRunner decides graph-level order.
  • Campaign route/restart policy is encoded in orchestrator YAML.
  • A local leaf id is emitted as app-level current_step.
  • Diagnosis/context is expected to infer or repair Orchestrator graph identity.
  • A produced Product is replaced by an Orchestrator failure because a later evidence handoff, projection, promotion, or identity check failed.
  • A post-Product runtime incident is written into Product status, reason, or reason_code instead of the separate execution channel.

16. Graph Deadline Compilation

Orchestrator is the only substrate component that owns the complete phase graph, so it publishes the graph watchdog-floor context. It starts that context with a finite discovery/contract-loading floor. Immediately before each admitted leaf it resolves that leaf from the payload which actually exists at that point and monotonically adds the conservative floor to the graph receipt. Nested orchestrators share the same context. The receipt may only move later; it must never shorten the operator-owned process watchdog after the Worker observes it.

Before graph execution, a strict registry requires every callable to classify itself as deadline_plan: direct or deadline_plan: resolver. Registry construction rejects missing classifications and validates every composite marker against its loaded public callable. The marker contains no topology or duration; it only makes missing or contradictory resolver declarations a startup contract failure. App CI additionally verifies the classification against the source call graph; runtime never guesses App behavior from source names or payload fields.

This progressive compilation is intentional. A later leaf's admitted call count may depend on an earlier Product which does not exist at process preflight. Orchestrator must not fabricate that Product or use a historic one to guess a precise completion time. Contracts declare only enough topology to prevent a shorter parent watchdog. Sequential admitted steps may be accumulated; concurrent work may be grouped into waves. The result remains a minimum safety floor, not a latency SLA or normal-flow termination trigger.

Dynamic/data-shaped leaves expose both:

  • a finite declared watchdog floor for graph preflight; and
  • a resolved floor based on the admitted input before the leaf starts child work.

If work can legally outlive that floor while progressing, the transport or leaf must retain its own inactivity watchdog or cooperative progress/checkpoint contract. Orchestrator must not launch it under a shorter fixed parent.

ToolLoopRunner.max_total_latency_ms is scoped to one run_batch() call. Since Orchestrator normally invokes leaves in separate batches, that value is not an orchestrator/module/subagent deadline and must not be summed in comments as if it were one. Orchestrator maintains one monotonic graph execution context and atomically exports its minimum parent watchdog epoch, resolved leaf receipts, status, and source to the process supervisor. The Worker uses only a bounded discovery window until the first valid receipt exists, then computes max(operator_hard_watchdog, graph_floor) and follows later monotonic extensions. It never lets a graph receipt shorten the operator watchdog.

This receipt is deliberately not an execution-identity event. A job-scoped receipt path correlates one supervisor with one graph, but the receipt must not allocate, select, or bind campaign_id, trace_id, pipeline_run_id, cycle_no, an App asset, or a Web Job. Campaign owns those identities and the CLI exposes durable early acceptance through campaign.accepted in --events-jsonl mode. A supervisor may consume both channels concurrently, but it must never use deadline content, a ToolLoop scan, directory mtime, or a newest-Trace heuristic as a substitute for the Campaign event.

The operator epoch is an initial minimum floor, not an expiry gate for future receipts. After a supervisor accepts a graph floor, receipt validation must use max(operator_hard_watchdog, last_accepted_graph_floor) as the current effective floor. If that floor is still in the future, expiration of the original operator epoch must not reject a later valid monotonic extension. A receipt may no longer extend a process only after the current effective floor has expired and process termination has begun.

The required containment is:

effective process watchdog >= operator hard watchdog
effective process watchdog >= graph minimum floor + shutdown tail
current parent floor >= admitted child floor

If a contract violation or process failure nevertheless occurs, Orchestrator records the runtime incident and preserves the last returned Product. It does not replace a child status/reason merely to explain the parent timeout.

Tests must cover nested sequential graphs, concurrent waves, conditional Diagnosis/repair branches, data-shaped resolved floors, Worker propagation, and the rule that graph receipts may extend but never shorten the operator watchdog. Supervisor contract tests must also cover a late extension received after the original operator epoch but before the previously accepted graph floor. They must also prove that deadline receipt consumption cannot create or change a Job/Trace binding and that Campaign JSONL identity remains an independent channel.

Single-tool admission, sequential batch, parallel batch, and Orchestrator leaf admission must use the same ToolLoop deadline compiler API. Orchestrator may add compiled leaf envelopes to the graph critical path; it must not call an App resolver directly or reproduce configured-floor fallback behavior.


17. Active Source App Boundary

Orchestrator may transport only explicitly declared generic envelope roles and bounded mechanical facts. It must never register, prioritize, extract, compare, or reinterpret fields because they are meaningful to a particular app.

App-owned diagnostic material enters through the explicit diagnosis_facts role. Product identity enters through explicit graph/projection coordinates. Continuation legality enters through the explicit result envelope. Unknown or ambiguous fields are not guessed, recursively scanned, or renamed into a hidden compatibility path.

Boundary tests must assert absence of app product names, app phase names, and app business field names from active Orchestrator Python. A renamed hardcode is still a boundary violation.