返回设计文档

ENGINE · 03/08

Orchestrator

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

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

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


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.


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
  • terminal_rules
  • 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. Every step also receives:

  • root payload;
  • graph identity;
  • parent graph identity;
  • graph contract context: step_order, step_deps, step_orchestrators,
  • implicit diagnosis phase set;
  • prior step results;
  • accumulated artifact_refs;
  • failed upstream context when entering a diagnosis phase.

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.

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.

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 asset execution attempt inside that campaign, starting from the orchestrator first step
pipeline_run_id = one full graph execution chain under a trace
step_run_id = one executable phase attempt inside that graph
cycle_no    = same trace / same asset / campaign-accepted restart attempt number

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 allocates a new trace_id whenever a campaign asset starts from the first orchestrator step.
  • 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.
  • 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 most recently updated checkpoint whose generic Product pointers all match that checkpoint's trace and cycle. 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.
  • 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.
  • 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.
  • An app may start an asset-birth graph without asset_id. Before asset birth, projection Products live in the unbound docs/ai-skills/<product>/<trace_id>/cycle-000N/ root.
  • Evidence facts, digest and handoff are written into that unbound root as soon as each Evidence stage completes. Orchestrator does not defer those writes or revisit them after asset birth; a successful bind atomically moves the whole trace and remaps current in-memory locators.
  • The single production step that creates the asset returns exactly one produced_asset_id candidate. Before adopting it, Orchestrator must request the generic Projection bind_unbound_trace(product, trace_id, asset_id) action and verify its complete receipt. Only then may it inject the identity into later steps, surface it through parent graphs, or use it for active promotion.
  • A complete bind receipt must be a successful nexrur_projection_active / bind_unbound_trace envelope whose inner action, product, trace and asset identities match exactly; it must also carry already_applied: bool, the sha256-tree-v1 content manifest, destination ref and that same destination in artifact_refs. A success label without these facts is a bind failure.
  • 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_binding 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.
  • produced_asset_id may fill an unbound graph only. If the graph is already bound, the returned value must equal that identity; a different value is an orchestrator contract failure. Multiple non-empty produced identities are always a conflict. Two production steps must never compete to bind one trace.
  • A production result whose projection_identity.scope is explicitly trace still requires bind_unbound_trace when the graph already carries the same asset_id. The pre-existing identity proves the allowed destination; it does not prove that this trace has moved out of the unbound product root.
  • Binding failure leaves the graph unbound and failed; Orchestrator must not publish/adopt the candidate identity or attempt active promotion. An exact already-bound receipt is idempotent success; a conflicting destination fails closed.
  • Bind 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 a verified bind, Orchestrator retargets current-cycle in-memory artifact locators from the receipt's exact unbound source_ref prefix to its bound destination_ref prefix before building the downstream payload. This changes locators only; Product bytes, checksums and the bind receipt remain untouched.
  • Orchestrator never infers unbound-trace archival from cycle count or missing asset_id. It may request archive_unbound_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.
  • 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 a campaign-owned asset attempt 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" or "one recursive graph call". A trace is a campaign-owned asset attempt; a cycle is a campaign-accepted restart inside that same attempt.

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:一个 asset 从 orchestrator 第一阶段开始的一条业务链。
  • 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/.audit/<trace_id>/cycle-000N/audit.jsonl
docs/ai-skills/<product>/<trace_id>/cycle-000N/...  # unbound asset-birth trace
docs/ai-skills/<product>/<asset_id>/<trace_id>/cycle-000N/...

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.coreai-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.

The public read-only checkpoint projection returns:

  • trace/campaign/pipeline/cycle identity and current root step;
  • 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
  -> rewind the selected root/nested state in place
  -> accumulate bounded successful receipts across exact resumes in that cycle
  -> 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

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 Product pointer is valid only when its declared trace and cycle still equal the current execution identity.

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

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.
  • 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;
  • 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, so StepSix Projection 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;

  • 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.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 binds an app-authorized unbound trace to an accepted asset identity, 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.py now exposes one active graph runner:

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.

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.

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.


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.