Rulvar API reference / @rulvar/core / OrchestrateSynthesis
Interface: OrchestrateSynthesis
Defined in: packages/core/src/orchestrator/orchestrate.ts:1850
The synthesis invocation's own knobs (RV-211). Everything else about the invocation is deterministic: the prompt derives from the journaled draft and the settled child digest, the toolset is the single finish tool (a distinct toolsetHash, exactly like the reserved cap finalizer), the invocation journals as an ordinary agent entry (a resume replays it with zero paid calls), and its telemetry is a full agent span with role 'synthesize' phase pairs, so CostReport.byRole.synthesize and reduceCriticalPath attribute it without heuristics. Failure posture: with finishValidation configured a failed synthesis fails the run typed (the validated path is mandatory); without validators the run falls back to the coordination draft under a journaled 'orchestrator_synthesis_fallback' decision and a warn log, never silently.
Properties
| Property | Type | Description | Defined in |
|---|---|---|---|
carryDraftGaps? | boolean | Carry a FAILED skip pre-pass into the synthesis prompt (RV808a). The pre-pass verdict used to be discarded on failure, and the twelfth comparison run paid for exactly that: synthesis re-derived the whole document blind to which validators the draft had already failed, then failed the same contract once more itself. With true, a failing pre-pass journals its verdict (decisionType 'orchestrator_synthesis_draft_gaps': the failed validator names with their reasons, bound to the contract generation and the draft hash exactly like the skip decision), and the synthesis prompt gains a DRAFT CONTRACT GAPS: line naming those failures with the instruction to repair the named gaps and preserve the draft otherwise. A resume reuses the journaled verdict without re-running a validator, so the prompt bytes re-derive identically and the paid invocation replays. Requires skipWhenDraftValid (the gaps ARE the pre-pass verdict; there is nothing to carry without it). Default false: no decision entry, prompt bytes identical. | packages/core/src/orchestrator/orchestrate.ts:2014 |
claimMap? | true | The atomic claim map of the composition (RV4305, P2.1). With true, the synthesis invocation's finish REQUIRES a typed claimMap beside the result: one row per material claim, each with its evidentiary grade (source, inference, assumption, live-observed), the source anchors it rests on, the inference bridge on inference rows, and the run evidence on live-observed rows. The finish tool's schema and description change under the opt-in, so the synthesis toolset hash moves BY DESIGN (the sectional precedent). Deterministic validation is STRUCTURAL only: every document anchor covered by the map and every map anchor present in the document (both directions), at most one non-source row per anchor (a row count, never a semantic verdict), per-grade required blocks, unique ids; a structural failure spends the ordinary finish repair bound like any validator rejection. Semantic truth stays with the judges: the accepted map is journaled beside the accepted candidate (linked by candidateHashOf) and fed into the existing claim judge's prompt under this same opt-in; no new judge and no new rounds exist. Requires finishValidation; refuses beside skipWhenDraftValid and fallbackToValidDraft (both can ship a DRAFT that never carried a map) and beside finishValidation.sectionalRepair (a sectional resubmission would splice a document out from under its map); an armed repair round resubmits the full document with a full map instead of arming the sectional shortcut. Absent, every byte holds: prompt, toolset hash, journal, envelope. | packages/core/src/orchestrator/orchestrate.ts:2103 |
context? | "full" | "digests" | What the 'single' synthesis prompt embeds beside the draft (the v1.74 experiment review, P0.2). Default 'digests': the 400 char settled digest rows, byte identical to pre 1.76. 'full' appends a CHILD OUTPUTS section carrying every settled child's FULL serialized output after the digest rows: the whole evidence pool the validators judge against rides the prompt, paid as input tokens (declare estCost or the preflight estInputTokens accordingly). | packages/core/src/orchestrator/orchestrate.ts:1968 |
dedupeClaims? | boolean | Deduplicate repeated claim lines across children BEFORE any model call (RV-211 remainder; default false, and the prompt stays byte identical when unset). In 'single' mode the digest entering the synthesis prompt keeps only the FIRST occurrence of every repeated line and a REPEATED CLAIMS index (each claim with its reporters) rides the prompt beside it. In 'incremental' mode the deterministic reconciliation dedupes the note texts the same way and the envelope carries the repeatedClaims index. Matching is whitespace-collapsed exact line equality: nothing fuzzy ever merges two distinct claims. | packages/core/src/orchestrator/orchestrate.ts:1936 |
effort? | Effort | Canonical effort of the synthesize invocation. | packages/core/src/orchestrator/orchestrate.ts:1854 |
estCost? | number | Admission estimate for the synthesize invocation, like AgentOpts.estCost: under a tight orchestrator cap the default reserve (full maxOutputTokens pricing) can refuse the dispatch; an explicit estimate is the host speaking. In 'incremental' mode the estimate applies to EACH note invocation. | packages/core/src/orchestrator/orchestrate.ts:1903 |
evidenceIndex? | | true | { flags?: string; pattern?: string; } | The structured evidence index (RV808b): a deterministic per-child citation map in the 'single' synthesis prompt, so the composing model can target its reads instead of re-reading the whole evidence pool (context: 'full' re-pays every child output as input tokens; the twelfth comparison run spent 357 s of synthesis on exactly that re-derivation). One EVIDENCE INDEX: line rides the prompt after the digest rows: per SETTLED child in spawn order, its nodeId, terminal status, the DISTINCT citations its output actually carries (matches of pattern, default DEFAULT_CITATION_PATTERN; extracted ONLY from evidence-pool children, ok and salvage-accepted, exactly the pool evidencePreservedValidator judges, so an indexed citation is never one the validators would reject as fabricated), its artifact descriptors (the read_child_artifact vocabulary), and its output size in chars. With exposeChildResultTools the rows carry the child handle, so the index and the pagination tools compose: read exactly the child whose citation you need. Folded ONLY from replay-stable settled results (the policyFacts precedent), so a resumed synthesis re-derives identical prompt bytes; true uses the default pattern, an object overrides it (fail-closed: a pattern that can match the empty string is refused at intake, the RV610 posture). Meaningless in 'incremental' mode (no single synthesis prompt exists): a ConfigError. Absent = the prompt stays byte identical. | packages/core/src/orchestrator/orchestrate.ts:2074 |
exposeChildResultTools? | boolean | Give the 'single' synthesis invocation the RV-201 evidence tools get_child_result and read_child_artifact beside finish (the v1.74 experiment review, P0.2): the finish validators hold the result against the FULL child outputs while the synthesis model sees 400 char digests, so when the coordination draft collapses the evidence the validators demand is model-invisible. With the tools exposed the digest rows in the synthesis prompt carry each child's handle, and the model pages any settled child's full output or artifacts before finishing. Off by default: the synthesis toolset and prompt stay byte identical, exactly like the coordination exposeChildResultTools. | packages/core/src/orchestrator/orchestrate.ts:1957 |
fallbackToValidDraft? | boolean | The no-regression floor under the synthesis (RV2505, the 1.226.0 comparison run). That run's coordination draft satisfied the FULL declared contract, skipWhenDraftValid was off because the operator wanted the composing pass anyway, and the synthesis then failed the same bundle three times and died mid repair: the run settled with NO result at all, having paid for four workers, the draft that would have passed, and three rejected compositions. With true, a synthesis that fails terminally does not throw away a draft the contract accepts. The failure is caught at the post-fan-in chokepoint, the coordination draft is judged by the same finishValidation.validators that bind the synthesis finish, and a draft every validator accepts becomes the run result under a journaled 'orchestrator_synthesis_regressed' decision (the failure message, the validator names, the draft hash, the contract generation) plus a warn 'orchestrator synthesis regressed' log; the envelope carries synthesisRegressed. A draft that fails too journals 'orchestrator_synthesis_fallback_declined' naming ITS failing validators and the original failure rethrows untouched, so the decline is auditable instead of silent. Deterministic by construction: only the declared contract judges, never a quality heuristic, and the verdict is a pure function of the draft, so a resume re-derives it without re-running the paid invocation. Requires finishValidation (a ConfigError at intake otherwise: without a contract there is nothing to judge either document by), which transitively limits it to mode 'single'. Orthogonal to skipWhenDraftValid: that gate decides whether to PAY for the synthesis, this floor decides what to do when the paid one comes back worse than the draft, and with both on a valid draft skips before there is anything to regress. Default false: no catch, no decision entry, no envelope field, byte for byte. | packages/core/src/orchestrator/orchestrate.ts:2047 |
instructions? | string | Extra deterministic instruction lines appended to the synthesis prompt. | packages/core/src/orchestrator/orchestrate.ts:1858 |
limits? | UsageLimits | UsageLimits of the synthesize invocation; default { maxTurns: 4 }. | packages/core/src/orchestrator/orchestrate.ts:1856 |
mode? | "single" | "incremental" | The synthesis shape (RV-211 remainder). Default 'single': one post-fan-in synthesize invocation composes the final result from the draft and the whole settled digest. 'incremental': every settled child triggers ONE bounded synthesize-role NOTE invocation as soon as it settles (concurrent with the still-running fan-out, which is what moves synthesis wall time off the post-fan-in critical path), and the FINAL result is a DETERMINISTIC reconciliation, never another model call: an IncrementalSynthesisResult envelope composed from the draft and the notes in spawn order. The tradeoffs are explicit: notes are paid DURING the run, so an acceptance rejection can no longer guarantee "a rejected run never paid for synthesis"; and because the reconciliation has no model-composed finish, finishValidation cannot bind it: configuring both is a ConfigError at intake. A note that dies falls back to the child's raw digest summary under a journaled per-child 'orchestrator_synthesis_note_fallback' decision and a warn log. Cap paths are unchanged: a capped run settles through the reserved finalizer and never reconciles. | packages/core/src/orchestrator/orchestrate.ts:1924 |
model? | ModelSpec | Model override for the synthesize invocation; the routing key and chain apply otherwise. | packages/core/src/orchestrator/orchestrate.ts:1852 |
noteLimits? | UsageLimits | UsageLimits of ONE incremental note invocation; default { maxTurns: 2 }. In mode 'single' the declaration is a typed ConfigError (RV3102): no note invocation exists for the limits to bound, and until the gate it was silently ignored. | packages/core/src/orchestrator/orchestrate.ts:1943 |
policyFacts? | boolean | Opt-in policy-facts line in the 'single' synthesis prompt (RV709): a deterministic digest of the settled children's durable tool-budget facts (statuses, extension grants, finalization windows and reserves), so the composing model can cite the run's own observed evidence instead of underclaiming it. Folded ONLY from replay-stable material (the settled results the journal replays verbatim), so a resumed synthesis re-derives identical prompt bytes; off by default, and the prompt stays byte identical when unset (prompt bytes are journal identity). | packages/core/src/orchestrator/orchestrate.ts:1870 |
runFacts? | | boolean | { workflowSoFar?: boolean; } | Opt-in RUN FACTS line in the 'single' synthesis prompt (RV1503), the policyFacts sibling: the aggregate of the settled children's replay-stable execution facts (executionFactsOf: wire requests, missing response ids, token totals, statuses), so the composing model can grade live-observed truthfully instead of erasing the run it is part of. The line names its own boundary (harness-observed, not production evidence). Folded ONLY from journal-replayed material; off by default, and the prompt stays byte identical when unset. The object form (RV3004) keeps the child line and adds opt-ins. workflowSoFar: true appends a RUN FACTS SO FAR line: the same counters folded over the settled children PLUS this orchestration's own settled internal spans as of this dispatch's composition (coordination turns, draft claim judges, judged contradiction passes, synthesis notes), so the number the model quotes sits next to the invoice instead of a third of it. The composing dispatch itself and anything still running are excluded by construction, the line says so, and dollars stay absent for the same replay reason as the child line. runFacts: true keeps today's prompt bytes exactly; the SO FAR line exists only under the object opt-in. | packages/core/src/orchestrator/orchestrate.ts:1895 |
skipWhenDraftValid? | boolean | The conditional synthesis gate (RV510, the ninth comparison experiment: synthesis returned the byte-identical draft after 101.3 s and 0.5512 USD, 57.3% of post-fan-in wall time). With true, before the 'single' synthesis span starts the coordination draft is run through the FULL declared finish contract (the same finishValidation.validators that would bind the synthesis finish): a draft that passes skips the synthesis invocation entirely under a journaled 'orchestrator_synthesis_skip' decision with reason 'synthesis_skipped_by_valid_draft' (the existing skip vocabulary; the info log and the acceptance envelope carry it), and a resume rolls the journaled skip forward with zero paid calls. A draft that fails any validator goes to synthesis exactly as before, with the repair budget untouched (the gate is a pre-pass, never a journaled validation verdict). Deterministic by construction: only the declared contract judges, never a semantic delta heuristic. Requires finishValidation (a ConfigError at intake otherwise: without a contract there is nothing to judge the draft valid by), which transitively limits it to mode 'single'. With a configured budget.synthesisReserveUsd the held money is released unconsumed on the skip and no reserve lifecycle journals: there was no synthesis invocation to account. Default false: the gate, the decision entry, and the envelope field are all absent, byte for byte. | packages/core/src/orchestrator/orchestrate.ts:1994 |