Status: active substrate design Owner:
nexrur.engines.orchestratorRelated 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.mdTracking: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:
| Field | Meaning |
|---|---|
pipeline.step_order | Ordered graph surface. |
pipeline.step_deps | DAG dependencies. |
pipeline.step_orchestrators | Mapping 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:
ownercallablesteps.<step>.callablestep_toolsstep_producesstep_consumesterminal_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:
- Build a global tool id index from
subagents.<block>.tools. - Reject duplicate tool ids across blocks.
- For a leaf step, find the matching tool id.
- Execute that tool through ToolLoopRunner / callable executor.
- 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_idfor each CLI run. - Campaign allocates a new
trace_idwhenever 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_idand advancecycle_no/cycle_segment. - An explicit checkpoint-backed
resumeis execution recovery, not a new Campaign attempt.resume + phase_pathrewinds exactly that phase and its downstream state inside the existing checkpoint; it preservestrace_id,pipeline_run_id,cycle_noandcycle_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_pathvalidates 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 asrestart_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 unbounddocs/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_idcandidate. Before adopting it, Orchestrator must request the generic Projectionbind_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_traceenvelope whose inner action, product, trace and asset identities match exactly; it must also carryalready_applied: bool, thesha256-tree-v1content manifest, destination ref and that same destination inartifact_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_bindingfield as authority. - At a completed product boundary, Orchestrator may request active promotion
only when the latest Schema result explicitly carries
validation_passed=true. Schemafalse/nullalready 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=schemasucceeds at agraph_role=subagentProduct boundary, Orchestrator must pass the exact returnedschema_envelopeto the generic Projection trace writer asenvelopes/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.jsonref still resolves to a file inside the sameapp_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_idmay 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.scopeis explicitlytracestill requiresbind_unbound_tracewhen the graph already carries the sameasset_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_idand graph-levelproduced_asset_idremain 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_refprefix to its bounddestination_refprefix 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 requestarchive_unbound_traceonly 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
normalizeresolves to its declared local executable entry such asnormalize_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_refsfrom successful pre-restart receipts to Projection.product_ref,upstream_product_refsand other Product identity pointers are never copy sources: a Product that declarestrace_id/cycle_nocannot 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_passedandactive_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 asprepare_fail2pivotfrom 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_pivothandoff. 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_idwas supplied. - Orchestrator must generate a fresh
step_run_idbefore every executable graph phase attempt. Leaf ToolLoop payloads must expose that id as bothstep_run_idand the runtimerun_id;pipeline_run_idremains 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_idin records, but that ID is not a directory segment. .engines、.core、ai-skills不允许各自发明一套身份目录。- Existing
trace_id/run_idortrace_id/cycle-000N/run_idlayouts are migration debt, not final contract. - Human replay should be able to follow the same
trace_id -> cyclepath 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_orderandstep_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 / aftercontext snapshots forcore / 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_stepis the app-level checkpoint entry.rerun_target.phase_pathis a path through nested graph steps declared inpipeline.step_orchestrators.rerun_target.target_leafis 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:
- Reset the top-level checkpoint from the resolved app entry.
- Persist the nested restart scope on the checkpoint or resume payload so it is available during the next cycle.
- When recursive execution enters a child graph whose parent phase matches the
next
phase_pathsegment, applystart_stepinside that child graph. - When the final graph is reached and
target_leafis present, use it as the final graphstart_step. - 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_leafis missing from the final graphstep_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:
modemust 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 asnormalizemust not leak into ToolLoop path comparison after they resolve to an executable leaf such asnormalize_evidence. - The target graph/leaf, caller and registered tool must match the resumed execution exactly before the handoff leaves Orchestrator.
async_operationis visible only while Orchestrator descends along its declaredtarget_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_eventsare 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_identityderived 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 fromtrace_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_identityagainst 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:
ToolLoopHardTimeoutdoes 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_stepfrom the app graph frame that owns the recursive execution. It is never copied from the current leaf tool id. phase_pathpreserves the declared nested graph path beneath that app step.phaseandfailed_phasepreserve local leaf identity; they do not replacecurrent_step.- Every nested child and Step6/fail2pivot payload must carry the same canonical
app-level
current_stepwhile 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.pyto 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_stepmust bind them explicitly; - a direct subagent/module graph call without a root App step must carry an
explicit
projection_productrequest 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_orchestratorso evidence assembly can distinguish direct upstream active truth from ancestor/reference material. build_evidence_bundle.pybuildsaiskills_read_planfrom payload context. It does not read ai-skills files and does not digest content.aiskills_read_plan.upstreamcontains only direct upstream active refs derived fromparent_orchestrator.step_deps[parent_orchestrator.phase]. Local phase trace refs and ancestor refs must go toreference, 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.pydoes not open projection layouts or hydrate a missingactive_manifest.json; the reader reports a missing required source according to the App source contract. read_aiskills_artifacts.pyexecutesaiskills_read_plan. It must not scan all active directories to infer upstream / diagnosis / reference.build_evidence_digest.pymaterializes the aiskills lanes:aiskills/upstream/, restart-onlyaiskills/diagnosis/step6.json, andaiskills/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_receiptper 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:beforeis the latest known five-bucket state,deltarecords execution status and produced artifact refs, andafteris 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:
- retain the producer result, including its original
status,reason,reason_code,failure_kind, refs, warnings, errors, and business fields; - record the later failure as execution status/code/errors or a bounded runtime incident in the existing execution channel;
- stop or block the graph when the execution contract requires it; and
- 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_schemais a mechanical validation tool.nexrur_fail2pivotis the Step6 diagnosis/campaign handoff tool and is recognized implicitly by the substrate orchestrator. App contracts should put it instep_order, not declarediagnosis_phases.nexrur_fail2pivotwrites Step6 trace artifacts and returns campaign control; it does not promote active.nexrur_projection_activeis 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_orchestratorsis a graph step, not a tool. A leaf step not instep_orchestratorsmust 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:
- load the current orchestrator contract;
- resolve child paths from
step_orchestrators; - build a global ToolLoop registry from
toolloop.yml; - execute nested graphs recursively;
- execute leaf tool ids through ToolLoopRunner;
- 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
- Keep app/subagent/module contracts minimal and human-auditable.
- Ensure every leaf step is a registered ToolLoop tool id.
- Ensure
step_orchestratorsonly points to childorchestrator.ymlfiles. - Add duplicate tool id validation to ToolLoop registry loading.
- Wire CLI/campaign to call
run_contract_graph(...). - Keep retired
skill*.pydispatch chains out of the active runtime path. - Keep app production semantics inside registered tools, not inside Orchestrator.
15. Anti-patterns
orchestrator.ymlcontainscallable.orchestrator.ymlcontains callable ownership outside requiredpipeline.graph_identity.owner.orchestrator.ymlcontainsstep_tools.orchestrator.ymlcontains 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*.pyowns the graph order after the recursive runner exists.- Orchestrator imports app
scripts/toolloops/*.pydirectly. - 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, orreason_codeinstead 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.