@rulvar/core 1.85.0 → 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3547,6 +3547,47 @@ interface ExplorationSummary {
3547
3547
  toolUnitsUsed?: number;
3548
3548
  }
3549
3549
  /**
3550
+ * The tool budget pressure snapshot (RV304, the seventh comparison
3551
+ * experiment): how close one agent invocation came to its tool budget,
3552
+ * visible BEFORE the terminal 'limit' a starved worker would settle
3553
+ * with. Attached to the full AgentResult and to the live `agent:end`
3554
+ * event whenever maxToolCalls, toolUnits, or toolBudgetExtension is
3555
+ * configured. Live telemetry only, exactly like transportRetries: never
3556
+ * journaled, absent on a replayed result.
3557
+ */
3558
+ interface ToolBudgetSummary {
3559
+ /** Executed tool calls (the loop's own counter). */
3560
+ used: number;
3561
+ /**
3562
+ * The effective executed-call cap at the end: maxToolCalls plus every
3563
+ * granted extension. Absent when only toolUnits bounds the loop.
3564
+ */
3565
+ cap?: number;
3566
+ /** Weighted units spent; present when toolUnits is configured. */
3567
+ unitsUsed?: number;
3568
+ /** The weighted budget; present when toolUnits is configured. */
3569
+ unitsMax?: number;
3570
+ /**
3571
+ * Extension grants used, restored grants included; present exactly
3572
+ * when toolBudgetExtension is configured (RV301).
3573
+ */
3574
+ extensionsGranted?: number;
3575
+ /**
3576
+ * Notice thresholds (fractions of the cap) whose notices entered the
3577
+ * conversation; present when at least one fired.
3578
+ */
3579
+ noticesFired?: number[];
3580
+ /** Present and true when the finalization reserve summary turn ran. */
3581
+ finalizationReserveUsed?: boolean;
3582
+ /**
3583
+ * Present and true when the finalization window activated at least
3584
+ * once this invocation (RV302).
3585
+ */
3586
+ finalizationWindowEntered?: boolean;
3587
+ /** The tool budget limiter that ended the loop, on that 'limit' only. */
3588
+ limiter?: "maxToolCalls" | "toolUnits";
3589
+ }
3590
+ /**
3550
3591
  * Agent lifecycle. One logical agent dispatch emits EXACTLY ONE
3551
3592
  * `agent:start`/`agent:end` pair on its span (the start carries the
3552
3593
  * primary role), and each model invocation phase inside the span
@@ -3632,6 +3673,12 @@ type AgentEvents = {
3632
3673
  * terminal error payload.
3633
3674
  */
3634
3675
  exploration?: ExplorationSummary;
3676
+ /**
3677
+ * The tool budget pressure snapshot (RV304). Present live whenever
3678
+ * a tool budget limiter or the extension was configured; live
3679
+ * telemetry only, absent on replay.
3680
+ */
3681
+ toolBudget?: ToolBudgetSummary;
3635
3682
  } | {
3636
3683
  type: "agent:error";
3637
3684
  agentType: string;
@@ -3668,11 +3715,12 @@ type ToolEvents = {
3668
3715
  rule?: Json;
3669
3716
  advisory?: Json;
3670
3717
  /**
3671
- * Present when an exploration guard (RV-210), not the permission
3672
- * chain, denied the call: the outcome is 'denied' and the call was
3673
- * never dispatched.
3718
+ * Present when an engine guard, not the permission chain, denied
3719
+ * the call: the exploration guards (RV-210) or the finalization
3720
+ * window (RV302). The outcome is 'denied' and the call was never
3721
+ * dispatched.
3674
3722
  */
3675
- guard?: "repeated-signature";
3723
+ guard?: "repeated-signature" | "per-tool-cap" | "finalization-window";
3676
3724
  };
3677
3725
  /**
3678
3726
  * Bare-nondeterminism detection (RV-209). Emitted LIVE by the segment
@@ -4041,6 +4089,50 @@ interface UsageLimits {
4041
4089
  finalizationReserve?: {
4042
4090
  maxOutputTokens?: number;
4043
4091
  };
4092
+ /**
4093
+ * The adaptive tool budget (RV301, the seventh comparison experiment):
4094
+ * when maxToolCalls expires but the run still has money and the agent
4095
+ * still makes progress, the runtime grants `increment` more executed
4096
+ * calls instead of ending the invocation, up to `maxExtensions` grants.
4097
+ * A grant is admitted only when the remaining chain budget (the same
4098
+ * arithmetic the per-turn output clamp reads) is above zero, or above
4099
+ * `minHeadroomUsd` when declared, and, unless `requireNewEvidence` is
4100
+ * set to false, only when at least one novel tool result digest arrived
4101
+ * since the previous grant (the exploration guard's evidence chain).
4102
+ * Each grant is announced to the model as a plain user message with the
4103
+ * exact new counts, so pacing stays possible; on resume the grants
4104
+ * re-derive conservatively from the restored executed-call count.
4105
+ * Extends maxToolCalls only, never toolUnits. Off by default: the
4106
+ * grant notices enter the conversation, so enabling it changes
4107
+ * recorded model requests.
4108
+ */
4109
+ toolBudgetExtension?: {
4110
+ /** Executed calls added per grant. */increment: number; /** Hard bound on grants per invocation. */
4111
+ maxExtensions: number; /** Grant only at or above this remaining chain headroom, in USD. */
4112
+ minHeadroomUsd?: number; /** Default true: a grant needs new evidence since the last one. */
4113
+ requireNewEvidence?: boolean;
4114
+ };
4115
+ /**
4116
+ * The finalization window (RV302, the seventh comparison experiment):
4117
+ * once the remaining tool budget (executed calls against the effective
4118
+ * maxToolCalls, or remaining weighted units against toolUnits.max,
4119
+ * whichever is closer) drops to `reserveCalls`, only finalization
4120
+ * tools may execute. A call outside the window's allowlist receives a
4121
+ * typed error tool result naming the window (visible to the model,
4122
+ * never terminal, consuming no budget), and the model is told ONCE,
4123
+ * via a plain user message, to record its evidence and finish. The
4124
+ * allowlist defaults to the tools priced at toolUnits cost 0 (the
4125
+ * free bookkeeping tools); the engine terminal tool is always
4126
+ * admitted regardless. With toolBudgetExtension configured, remaining
4127
+ * money converts into a grant BEFORE any window refusal, so the
4128
+ * window binds only when the extension is exhausted or denied. Off by
4129
+ * default: the refusals and the notice enter the conversation, so
4130
+ * enabling it changes recorded model requests.
4131
+ */
4132
+ finalizationWindow?: {
4133
+ /** How many trailing executed calls (or units) the window reserves. */reserveCalls: number; /** Tool names allowed inside the window; default: zero-cost tools. */
4134
+ allow?: string[];
4135
+ };
4044
4136
  }
4045
4137
  declare const DEFAULT_MAX_TURNS = 32;
4046
4138
  declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 12e4;
@@ -4064,6 +4156,16 @@ interface EffectiveUsageLimits {
4064
4156
  finalizationReserve?: {
4065
4157
  maxOutputTokens?: number;
4066
4158
  };
4159
+ toolBudgetExtension?: {
4160
+ increment: number;
4161
+ maxExtensions: number;
4162
+ minHeadroomUsd?: number;
4163
+ requireNewEvidence?: boolean;
4164
+ };
4165
+ finalizationWindow?: {
4166
+ reserveCalls: number;
4167
+ allow?: string[];
4168
+ };
4067
4169
  }
4068
4170
  /**
4069
4171
  * Limits merge per spawn: AgentOpts.limits over profile limits over engine
@@ -4196,6 +4298,13 @@ interface AgentResult<T> {
4196
4298
  */
4197
4299
  exploration?: ExplorationSummary;
4198
4300
  /**
4301
+ * The tool budget pressure snapshot (RV304): present live whenever
4302
+ * maxToolCalls, toolUnits, or toolBudgetExtension is configured. Live
4303
+ * telemetry only, exactly like transportRetries: never journaled,
4304
+ * absent on a replayed result.
4305
+ */
4306
+ toolBudget?: ToolBudgetSummary;
4307
+ /**
4199
4308
  * The structured terminal partial (RV-210 close-out): the LAST
4200
4309
  * successful `report_progress` call of the invocation, present only on
4201
4310
  * a 'limit' terminal (cap expiry or an engine-decided abort) whose
@@ -4270,6 +4379,13 @@ interface BudgetHooks {
4270
4379
  * or free output).
4271
4380
  */
4272
4381
  maxAffordableOutputTokens?: (servedBy: ModelRef, estimatedInputTokens: number) => number | undefined;
4382
+ /**
4383
+ * The remaining chain headroom in USD (RV301): the same arithmetic
4384
+ * the output bound above reads, before pricing. Undefined = no
4385
+ * ceiling anywhere on the chain. The tool budget extension admits a
4386
+ * grant against it.
4387
+ */
4388
+ remainingUsd?: () => number | undefined;
4273
4389
  /** Live usage accounting; layer 3 may respond by aborting `signal`. */
4274
4390
  onUsage(usage: Usage, servedBy: ModelRef): void;
4275
4391
  /** Layer 3: the ceiling AbortSignal. */
@@ -5122,6 +5238,14 @@ declare class RunBudget {
5122
5238
  * onUsage covers that hole), or when output is free. Zero or negative
5123
5239
  * means the turn cannot be dispatched within the budget.
5124
5240
  */
5241
+ /**
5242
+ * The tightest chain headroom of `accountScope` in plain USD (RV301):
5243
+ * exactly the remaining money the output clamp below prices, before
5244
+ * any pricing. Undefined when every account on the chain is uncapped;
5245
+ * never negative. The tool budget extension admits a grant against
5246
+ * this number.
5247
+ */
5248
+ remainingUsd(accountScope?: string): number | undefined;
5125
5249
  maxAffordableOutputTokens(servedBy: ModelRef, estimatedInputTokens: number, accountScope?: string): number | undefined;
5126
5250
  /**
5127
5251
  * Live accounting; spend propagates from `accountScope` to every
@@ -9141,6 +9265,19 @@ interface PreflightOrchestratorSpec {
9141
9265
  */
9142
9266
  extension?: boolean;
9143
9267
  /**
9268
+ * The OrchestrateAcceptance slice the estimator judges (RV305):
9269
+ * declaring it lets preflight relate capped children to the salvage
9270
+ * arms. Absent, the salvage findings stay silent, exactly like every
9271
+ * other undeclared input.
9272
+ */
9273
+ acceptance?: {
9274
+ childPolicy?: "all-ok" | {
9275
+ minSuccessful: number;
9276
+ };
9277
+ acceptPartialChildren?: boolean;
9278
+ acceptValidatedTerminalOutputOnLimit?: boolean;
9279
+ };
9280
+ /**
9144
9281
  * The separate synthesis invocation (RV-211), when the orchestration
9145
9282
  * configures one (the v1.71 experiment review: the run ceiling used
9146
9283
  * to stop at the coordination loop, undercounting the synthesis
@@ -9766,6 +9903,11 @@ interface AgentInvocationRow {
9766
9903
  costUsd: number;
9767
9904
  usageApprox: boolean;
9768
9905
  retryCount: number;
9906
+ /**
9907
+ * The tool budget pressure snapshot (RV304), carried through from the
9908
+ * live agent:end. Absent on replayed rows and unbounded loops.
9909
+ */
9910
+ toolBudget?: ToolBudgetSummary;
9769
9911
  replayed: boolean;
9770
9912
  /** True when the span's agent:end never arrived. */
9771
9913
  open: boolean;
@@ -9889,4 +10031,4 @@ interface SandboxBridge {
9889
10031
  declare const SANDBOX_AGENT_OPT_KEYS: readonly string[];
9890
10032
  declare function createSandboxBridge(ctx: Ctx<never>, options: SandboxBridgeOptions): SandboxBridge;
9891
10033
  //#endregion
9892
- export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, type AgentInvocationRow, AgentOpts, AgentProfile, AgentProfilePermissions, AgentProfileTemplateOptions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, AuditCategory, AuditRecord, AuditRunsOptions, BUDGET_ABORT_REASON, BaseAppend, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetExhaustionDiagnostics, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildArtifactPage, ChildIdentityInput, ChildResultPage, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostAttributionFacts, CostReport, CreateEngineOptions, type CriticalPath, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_CITATION_SAMPLE, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DataKeyProvider, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DedupedClaims, DerivedKey, DeriverRegistry, type DeterminismConfig, DeterminismError, type DeterminismEvents, type DeterminismMode, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EngineQuotaConfig, EngineQuotaRuntime, EntryKind, EntryRef, EntryStatus, EnvelopeEncryption, EnvelopeEncryptionOptions, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDecisionAbortedError, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, type ExecutorRegistry, type ExplorationSummary, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FencedCodeMode, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishContract, FinishContractCitations, FinishContractGoldenReject, FinishContractManifest, FinishInfo, FinishSelfTestFailure, FinishSelfTestFixtures, FinishSelfTestReport, FinishValidationChild, FinishValidationInput, FinishValidationSpec, FinishValidationVerdict, FinishValidator, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, IncrementalSynthesisResult, InvalidResolutionError, InvocationRole, type InvocationTable, InvoiceExport, InvoiceReconciliation, InvoiceRow, type IsolatedExecContext, type IsolatedExecRequest, type IsolatedExecutorTag, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JOURNAL_ENVELOPE_MARKER, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationContext, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, MatchResult, McpConfig, McpToolSource, MechanicalGateProfile, MechanicalGateVerdict, MemoryQuotaLimiter, type MetaLookupStore, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateAcceptance, OrchestrateOptions, OrchestrateSynthesis, OrchestrateSynthesisSkipReason, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, type PhaseRow, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PreflightAdmissionRow, PreflightFinding, PreflightInput, PreflightOrchestratorSpec, PreflightReport, PreflightSpawnReport, PreflightSpawnSpec, PreflightToolCeiling, PriceTable, PricedUsage, type Pricing, type PricingTier, ProgressReport, type ProviderAdapter, ProviderCallRecord, QUOTA_WINDOW_MS, QualityFloors, QuotaCounters, type QuotaDecision, type QuotaEstimate, type QuotaLimiter, type QuotaReservationRequest, QuotaRule, QuotaWindowSnapshot, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, RandIdentityInput, RandPayload, RateLimitObservation, ReconcileOptions, ReconcileResult, RefEntryAppender, RefEntryClassification, RefusalInfo, RepeatedClaim, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, RepositoryResearchToolset, RepositoryResearchToolsetOptions, ResearchAgentProfileOptions, ResearchAgentProfileResult, ResearchEvidenceEntry, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunAuditVerdict, RunBudget, RunEventSink, RunExport, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStateAudit, RunStatus, RuntimeEventSink, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, SecretMasker, SectionMatchMode, Semaphore, SerializationHook, Settled, SettlementError, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, type ToolExecutorProvider, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispatchProjectionReserveUsd, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, finishContract, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, headingStructureValidator, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, orchestratorAdmissionEstCostUsd, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, sectionCitationsValidator, selectStructuredOutputTier, selfTestFinishValidation, shouldCompact, snapshotUsage, spawnDepthOf, stripFencedBlocks, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, wordCountValidator, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
10034
+ export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, type AgentInvocationRow, AgentOpts, AgentProfile, AgentProfilePermissions, AgentProfileTemplateOptions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, AuditCategory, AuditRecord, AuditRunsOptions, BUDGET_ABORT_REASON, BaseAppend, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, BudgetExhaustionDiagnostics, BudgetHooks, BudgetReserve, type Bytes, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, CacheHint, CacheTtl, CanUseTool, CanonicalId, CanonicalIdentity, CanonicalLadderSpec, CanonicalModelSpec, ChatEvent, ChatRequest, CheckpointState, ChildArtifactPage, ChildIdentityInput, ChildResultPage, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostAttributionFacts, CostReport, CreateEngineOptions, type CriticalPath, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_CITATION_SAMPLE, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DataKeyProvider, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DedupedClaims, DerivedKey, DeriverRegistry, type DeterminismConfig, DeterminismError, type DeterminismEvents, type DeterminismMode, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EngineQuotaConfig, EngineQuotaRuntime, EntryKind, EntryRef, EntryStatus, EnvelopeEncryption, EnvelopeEncryptionOptions, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDecisionAbortedError, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, type ExecutorRegistry, type ExplorationSummary, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FencedCodeMode, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishContract, FinishContractCitations, FinishContractGoldenReject, FinishContractManifest, FinishInfo, FinishSelfTestFailure, FinishSelfTestFixtures, FinishSelfTestReport, FinishValidationChild, FinishValidationInput, FinishValidationSpec, FinishValidationVerdict, FinishValidator, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, IncrementalSynthesisResult, InvalidResolutionError, InvocationRole, type InvocationTable, InvoiceExport, InvoiceReconciliation, InvoiceRow, type IsolatedExecContext, type IsolatedExecRequest, type IsolatedExecutorTag, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JOURNAL_ENVELOPE_MARKER, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, JournalSerializationContext, JournalSerializationHook, type JournalStore, type Json, JsonSchema, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, type KbProposal, type KbProposalTrigger, KeyDeriver, KeyRing, KeyedLimiter, KnowledgeCasError, type KnowledgeSnapshot, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LadderSpec, type LeasableStore, type Lease, LeaseHeldError, Ledger, LineageCounters, LineageIndex, LineageRef, LineageRelation, LineageStats, LogicalTaskId, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, MatchResult, McpConfig, McpToolSource, MechanicalGateProfile, MechanicalGateVerdict, MemoryQuotaLimiter, type MetaLookupStore, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateAcceptance, OrchestrateOptions, OrchestrateSynthesis, OrchestrateSynthesisSkipReason, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, type PhaseRow, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PreflightAdmissionRow, PreflightFinding, PreflightInput, PreflightOrchestratorSpec, PreflightReport, PreflightSpawnReport, PreflightSpawnSpec, PreflightToolCeiling, PriceTable, PricedUsage, type Pricing, type PricingTier, ProgressReport, type ProviderAdapter, ProviderCallRecord, QUOTA_WINDOW_MS, QualityFloors, QuotaCounters, type QuotaDecision, type QuotaEstimate, type QuotaLimiter, type QuotaReservationRequest, QuotaRule, QuotaWindowSnapshot, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, RandIdentityInput, RandPayload, RateLimitObservation, ReconcileOptions, ReconcileResult, RefEntryAppender, RefEntryClassification, RefusalInfo, RepeatedClaim, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, RepositoryResearchToolset, RepositoryResearchToolsetOptions, ResearchAgentProfileOptions, ResearchAgentProfileResult, ResearchEvidenceEntry, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunAuditVerdict, RunBudget, RunEventSink, RunExport, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStateAudit, RunStatus, RuntimeEventSink, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, SecretMasker, SectionMatchMode, Semaphore, SerializationHook, Settled, SettlementError, ShellPatternRules, ShellSegment, ShellVerdict, SinglePhaseAppend, SpanMinter, SpanRegistry, SpawnAdmissionValue, SpawnAgentParams, SpawnKey, SpawnLineage, SpawnLineageOpt, SpawnOrigin, SpawnRecord, Spend, Stage, type StandardJSONSchemaV1, type StandardSchemaV1, StepIdentityInput, StructuredOutputTier, SuspendedAppend, SuspensionState, TOOL_NAME_PATTERN, type TaskClass, TaskDigest, TaskSpec, TerminalPatch, TerminationAccount, TerminationAccountSnapshot, TerminationDeniedValue, TerminationDeniedWriter, TerminationInitValue, TerminationLimits, TerminationResource, type ToolBudgetSummary, ToolCallRequest, ToolChoice, type ToolContext, ToolContextSeed, ToolContract, type ToolDef, type ToolEvents, type ToolExecutor, type ToolExecutorProvider, ToolInit, type ToolRisk, ToolRuntime, type ToolSource, type ToolSourceSession, ToolsOption, TranscriptSerializationHook, type TranscriptStore, TriggerClass, TtlState, Usage, UsageLimits, UsageSlice, VerifiedRecommendation, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, WakeBudgetBlock, WakeDigest, WakeTrigger, WireError, Workflow, WorkflowCallOpts, type WorkflowEvent, type WorkflowEventBody, WorkflowRegistry, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileSecretMasker, compileVerifiedLayer, constantTimeEqual, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createEnvelopeEncryption, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispatchProjectionReserveUsd, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, finishContract, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, headingStructureValidator, identityJcs, implementationAgentProfile, invoiceFromJournal, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, localKeyProvider, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, orchestratorAdmissionEstCostUsd, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, preflightEstimate, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceAuditTrail, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, sectionCitationsValidator, selectStructuredOutputTier, selfTestFinishValidation, shouldCompact, snapshotUsage, spawnDepthOf, stripFencedBlocks, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, wordCountValidator, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/dist/index.js CHANGED
@@ -9021,6 +9021,10 @@ function mergeUsageLimits(call, profile, engine) {
9021
9021
  if (toolUnits !== void 0) merged.toolUnits = toolUnits;
9022
9022
  const finalizationReserve = pick("finalizationReserve");
9023
9023
  if (finalizationReserve !== void 0) merged.finalizationReserve = finalizationReserve;
9024
+ const toolBudgetExtension = pick("toolBudgetExtension");
9025
+ if (toolBudgetExtension !== void 0) merged.toolBudgetExtension = toolBudgetExtension;
9026
+ const finalizationWindow = pick("finalizationWindow");
9027
+ if (finalizationWindow !== void 0) merged.finalizationWindow = finalizationWindow;
9024
9028
  return merged;
9025
9029
  }
9026
9030
  /**
@@ -9066,6 +9070,25 @@ function validateUsageLimits(limits, site) {
9066
9070
  const { maxOutputTokens } = reserve;
9067
9071
  if (maxOutputTokens !== void 0) requirePositiveInteger(maxOutputTokens, `${site}.finalizationReserve.maxOutputTokens`);
9068
9072
  }
9073
+ if (limits.toolBudgetExtension !== void 0) {
9074
+ const extension = limits.toolBudgetExtension;
9075
+ if (typeof extension !== "object" || extension === null || Array.isArray(extension)) throw new ConfigError(`${site}.toolBudgetExtension must be { increment, maxExtensions, minHeadroomUsd?, requireNewEvidence? }`);
9076
+ const { increment, maxExtensions, minHeadroomUsd, requireNewEvidence } = extension;
9077
+ requirePositiveInteger(increment, `${site}.toolBudgetExtension.increment`);
9078
+ requirePositiveInteger(maxExtensions, `${site}.toolBudgetExtension.maxExtensions`);
9079
+ if (minHeadroomUsd !== void 0 && (typeof minHeadroomUsd !== "number" || !Number.isFinite(minHeadroomUsd) || minHeadroomUsd < 0)) throw new ConfigError(`${site}.toolBudgetExtension.minHeadroomUsd must be a finite nonnegative USD amount, got ${typeof minHeadroomUsd === "number" ? String(minHeadroomUsd) : typeof minHeadroomUsd}`);
9080
+ if (requireNewEvidence !== void 0 && typeof requireNewEvidence !== "boolean") throw new ConfigError(`${site}.toolBudgetExtension.requireNewEvidence must be a boolean; got ${typeof requireNewEvidence}`);
9081
+ }
9082
+ if (limits.finalizationWindow !== void 0) {
9083
+ const window = limits.finalizationWindow;
9084
+ if (typeof window !== "object" || window === null || Array.isArray(window)) throw new ConfigError(`${site}.finalizationWindow must be { reserveCalls, allow? }`);
9085
+ const { reserveCalls, allow } = window;
9086
+ requirePositiveInteger(reserveCalls, `${site}.finalizationWindow.reserveCalls`);
9087
+ if (allow !== void 0) {
9088
+ if (!Array.isArray(allow)) throw new ConfigError(`${site}.finalizationWindow.allow must be an array of tool names`);
9089
+ for (const [index, name] of allow.entries()) if (typeof name !== "string" || name.length === 0) throw new ConfigError(`${site}.finalizationWindow.allow[${String(index)}] must be a nonempty tool name`);
9090
+ }
9091
+ }
9069
9092
  }
9070
9093
  //#endregion
9071
9094
  //#region src/model/failover.ts
@@ -9646,9 +9669,15 @@ const DEFAULT_MODEL_RETRY_ATTEMPTS = 2;
9646
9669
  */
9647
9670
  /** The docs anchor cited by guard denials and the guard abort. */
9648
9671
  const GUARD_DOCS_URL = "https://docs.rulvar.com/guide/agents#exploration-guards";
9649
- /** True when any exploration guard field asks for tracking. */
9672
+ /** The docs anchor cited by finalization window refusals (RV302). */
9673
+ const WINDOW_DOCS_URL = "https://docs.rulvar.com/guide/agents#the-finalization-window";
9674
+ /**
9675
+ * True when any exploration guard field asks for tracking. The tool
9676
+ * budget extension (RV301) counts too: its requireNewEvidence admission
9677
+ * reads the guard's evidence chain.
9678
+ */
9650
9679
  function explorationTrackingEnabled(limits) {
9651
- return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true || limits.maxCallsPerTool !== void 0 || limits.toolUnits !== void 0;
9680
+ return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true || limits.maxCallsPerTool !== void 0 || limits.toolUnits !== void 0 || limits.toolBudgetExtension !== void 0;
9652
9681
  }
9653
9682
  function digestOf$1(value) {
9654
9683
  try {
@@ -9775,6 +9804,25 @@ var ExplorationGuard = class {
9775
9804
  unitsExhausted() {
9776
9805
  return this.config.toolUnits !== void 0 && this.unitsUsed >= this.config.toolUnits.max;
9777
9806
  }
9807
+ /**
9808
+ * Weighted units still spendable, floored at zero; undefined without
9809
+ * toolUnits configured. The finalization window (RV302) reads this on
9810
+ * every call evaluation, so it stays an O(1) counter, unlike the
9811
+ * allocating summary().
9812
+ */
9813
+ unitsRemaining() {
9814
+ return this.config.toolUnits === void 0 ? void 0 : Math.max(0, this.config.toolUnits.max - this.unitsUsed);
9815
+ }
9816
+ /**
9817
+ * Distinct successful result digests seen this invocation: the
9818
+ * extension's requireNewEvidence admission compares snapshots of this
9819
+ * count (RV301). A result JCS cannot digest never counts, so a grant
9820
+ * fails closed there, unlike the guards, which fail open: a denied
9821
+ * grant only restores the pre-extension expiry.
9822
+ */
9823
+ evidenceCount() {
9824
+ return this.seenDigests.size;
9825
+ }
9778
9826
  /** The abort message for a tripped no-new-evidence guard. */
9779
9827
  describeTrip() {
9780
9828
  return `exploration guard: ${String(this.noNewEvidenceStreak)} consecutive tool calls returned no new evidence (maxNoNewEvidenceCalls ${String(this.config.maxNoNewEvidenceCalls ?? this.noNewEvidenceStreak)}; every result was already seen this invocation). The executed work is kept; narrow the scope, vary the queries, or raise the limit (${GUARD_DOCS_URL}).`;
@@ -9813,6 +9861,30 @@ function toolBudgetNoticeText(used, max) {
9813
9861
  const remaining = Math.max(0, max - used);
9814
9862
  return `Tool budget notice: ${String(used)} of ${String(max)} tool calls used; ${String(remaining)} remaining. Prioritize the highest value calls and finish with what you have.`;
9815
9863
  }
9864
+ /**
9865
+ * The model-visible extension notice (RV301): announces one grant with
9866
+ * the exact new counts. Deterministic for given counts, like the budget
9867
+ * notice above.
9868
+ */
9869
+ function toolBudgetExtensionNoticeText(grant, maxExtensions, used, cap) {
9870
+ const remaining = Math.max(0, cap - used);
9871
+ return `Tool budget extended: grant ${String(grant)} of ${String(maxExtensions)}; ${String(used)} of ${String(cap)} tool calls used, ${String(remaining)} remaining. The budget headroom permits continued work; prioritize the highest value calls.`;
9872
+ }
9873
+ /**
9874
+ * The one-time model-visible window notice (RV302). Deterministic for
9875
+ * given counts, like the notices above.
9876
+ */
9877
+ function finalizationWindowNoticeText(remaining, reserve, budget) {
9878
+ return `Finalization window: ${String(Math.max(0, remaining))} of the reserved final ${String(reserve)} ${budget} remain. Only finalization tools (and the terminal tool) may execute now; record your evidence and finish with what you have.`;
9879
+ }
9880
+ /**
9881
+ * The typed window refusal a non-allowlisted call receives (RV302):
9882
+ * the same posture as the guard denials above, visible to the model,
9883
+ * never terminal, consuming no budget.
9884
+ */
9885
+ function finalizationWindowRefusalText(name, reserve, budget) {
9886
+ return `finalization window: the last ${String(reserve)} ${budget} are reserved for finalization tools, and '${name}' is not in the window allowlist. Record your evidence with the allowed tools and call the terminal tool (${WINDOW_DOCS_URL}).`;
9887
+ }
9816
9888
  //#endregion
9817
9889
  //#region src/runtime/no-progress.ts
9818
9890
  /**
@@ -10601,13 +10673,32 @@ async function runAgent(options) {
10601
10673
  const noProgress = new NoProgressDetector(limits.noProgressTurns);
10602
10674
  const guard = explorationTrackingEnabled(limits) ? new ExplorationGuard(limits) : void 0;
10603
10675
  /**
10676
+ * The adaptive tool budget (RV301, the seventh comparison experiment):
10677
+ * the run that motivated it starved two mandatory workers at a fixed
10678
+ * 84-call cap while 38% of the USD ceiling sat unspent. A grant at the
10679
+ * expiry converts that headroom into `increment` more executed calls,
10680
+ * bounded by `maxExtensions`, admitted only with money remaining and
10681
+ * (by default) new evidence since the last grant. Grants re-derive
10682
+ * conservatively from the restored executed-call count on resume, so
10683
+ * nothing new is journaled or checkpointed.
10684
+ */
10685
+ const extension = limits.toolBudgetExtension;
10686
+ let extensionGrants = 0;
10687
+ let extensionEvidenceAtLastGrant = 0;
10688
+ const pendingExtensionNotices = [];
10689
+ const effectiveMaxToolCalls = () => limits.maxToolCalls === void 0 ? void 0 : extension === void 0 ? limits.maxToolCalls : limits.maxToolCalls + extensionGrants * extension.increment;
10690
+ /** The limiter that ended the loop; rides the RV304 pressure snapshot. */
10691
+ let limitLimiter;
10692
+ /** True once the finalization reserve summary turn actually ran. */
10693
+ let reserveSummaryRan = false;
10694
+ /**
10604
10695
  * The exact limiter behind a tool-budget expiry, with its counts: the
10605
10696
  * wording rides the finalization-reserve instruction and the 'limit'
10606
10697
  * terminal's errorMessage (P1.1 criterion: the terminal names the
10607
10698
  * limiter, never a bare status).
10608
10699
  */
10609
10700
  const toolBudgetDetail = (limiter) => {
10610
- if (limiter === "maxToolCalls") return `maxToolCalls (${String(toolCallsUsed)}/${String(limits.maxToolCalls ?? 0)})`;
10701
+ if (limiter === "maxToolCalls") return `maxToolCalls (${String(toolCallsUsed)}/${String(effectiveMaxToolCalls() ?? 0)})`;
10611
10702
  const max = limits.toolUnits?.max ?? 0;
10612
10703
  const used = guard === void 0 ? max : guard.summary(toolCallsUsed).toolUnitsUsed ?? max;
10613
10704
  return `toolUnits (${String(used)}/${String(max)})`;
@@ -10639,6 +10730,115 @@ async function runAgent(options) {
10639
10730
  }]
10640
10731
  });
10641
10732
  };
10733
+ /**
10734
+ * One extension grant (RV301), attempted exactly at a maxToolCalls
10735
+ * expiry inside the dispatch walk. The notice text is queued rather
10736
+ * than pushed: a user message may not interleave a tool batch, so the
10737
+ * queue flushes with the budget notices after the batch's results
10738
+ * join the history.
10739
+ */
10740
+ const tryToolBudgetGrant = () => {
10741
+ if (extension === void 0 || limits.maxToolCalls === void 0) return false;
10742
+ if (extensionGrants >= extension.maxExtensions) return false;
10743
+ if (extension.requireNewEvidence !== false) {
10744
+ if ((guard?.evidenceCount() ?? 0) <= extensionEvidenceAtLastGrant) return false;
10745
+ }
10746
+ const remaining = options.budget?.remainingUsd?.();
10747
+ if (remaining !== void 0) {
10748
+ const floor = extension.minHeadroomUsd ?? 0;
10749
+ if (floor > 0 ? remaining < floor : remaining <= 0) return false;
10750
+ }
10751
+ extensionGrants += 1;
10752
+ extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
10753
+ const cap = limits.maxToolCalls + extensionGrants * extension.increment;
10754
+ pendingExtensionNotices.push(toolBudgetExtensionNoticeText(extensionGrants, extension.maxExtensions, toolCallsUsed, cap));
10755
+ events?.emit({
10756
+ type: "log",
10757
+ level: "info",
10758
+ msg: `tool budget extended (grant ${String(extensionGrants)}/${String(extension.maxExtensions)}): maxToolCalls now ${String(cap)}`
10759
+ });
10760
+ return true;
10761
+ };
10762
+ const flushExtensionNotices = () => {
10763
+ for (const text of pendingExtensionNotices.splice(0)) messages.push({
10764
+ role: "user",
10765
+ parts: [{
10766
+ type: "text",
10767
+ text
10768
+ }]
10769
+ });
10770
+ };
10771
+ /**
10772
+ * The finalization window (RV302): once the remaining tool budget
10773
+ * drops to reserveCalls, only the allowlisted finalization tools (and
10774
+ * the always-admitted terminal tool) execute; everything else gets a
10775
+ * typed refusal that consumes nothing. The run that motivated it
10776
+ * recorded 10 of 14 evidence entries before the cap: one summary turn
10777
+ * cannot dump a backlog, a reserved tail of bookkeeping calls can.
10778
+ */
10779
+ const finalizationWindow = limits.finalizationWindow;
10780
+ let windowEntered = false;
10781
+ let windowNoticeFired = false;
10782
+ const pendingWindowNotices = [];
10783
+ /** The tightest remaining budget and which dimension provides it. */
10784
+ const windowRemaining = () => {
10785
+ let best;
10786
+ const cap = effectiveMaxToolCalls();
10787
+ if (cap !== void 0) best = {
10788
+ remaining: Math.max(0, cap - toolCallsUsed),
10789
+ budget: "tool calls"
10790
+ };
10791
+ const units = guard?.unitsRemaining();
10792
+ if (units !== void 0 && (best === void 0 || units < best.remaining)) best = {
10793
+ remaining: units,
10794
+ budget: "tool units"
10795
+ };
10796
+ return best;
10797
+ };
10798
+ const windowActive = () => {
10799
+ if (finalizationWindow === void 0) return;
10800
+ const state = windowRemaining();
10801
+ return state !== void 0 && state.remaining <= finalizationWindow.reserveCalls ? state : void 0;
10802
+ };
10803
+ /**
10804
+ * Marks the entry and queues the one-time notice. Queued, not pushed:
10805
+ * a user message may not interleave a tool batch, so the queue
10806
+ * flushes with the other notices after the batch's results join the
10807
+ * history. On resume the flags re-derive from the restored counts and
10808
+ * the notice (already in the restored messages) never re-fires.
10809
+ */
10810
+ const maybeMarkWindowEntry = () => {
10811
+ if (finalizationWindow === void 0 || windowNoticeFired) return;
10812
+ const state = windowActive();
10813
+ if (state === void 0) return;
10814
+ windowEntered = true;
10815
+ windowNoticeFired = true;
10816
+ pendingWindowNotices.push(finalizationWindowNoticeText(state.remaining, finalizationWindow.reserveCalls, state.budget));
10817
+ events?.emit({
10818
+ type: "log",
10819
+ level: "info",
10820
+ msg: `finalization window entered: ${String(state.remaining)} of the reserved final ${String(finalizationWindow.reserveCalls)} ${state.budget} remain`
10821
+ });
10822
+ };
10823
+ const flushWindowNotices = () => {
10824
+ for (const text of pendingWindowNotices.splice(0)) messages.push({
10825
+ role: "user",
10826
+ parts: [{
10827
+ type: "text",
10828
+ text
10829
+ }]
10830
+ });
10831
+ };
10832
+ /**
10833
+ * The window allowlist. The terminal and escalate tools never reach
10834
+ * this check: the dispatch walk intercepts both before the window
10835
+ * block, so the exits are structurally exempt rather than listed.
10836
+ */
10837
+ const windowAllows = (name) => {
10838
+ const allow = finalizationWindow?.allow;
10839
+ if (allow !== void 0) return allow.includes(name);
10840
+ return limits.toolUnits?.costs?.[name] === 0;
10841
+ };
10642
10842
  const modelRetryCounts = /* @__PURE__ */ new Map();
10643
10843
  let lastTurnUsage = {
10644
10844
  inputTokens: 0,
@@ -10671,6 +10871,14 @@ async function runAgent(options) {
10671
10871
  });
10672
10872
  guard?.restore(messages);
10673
10873
  if (limits.toolBudgetNotices === true && limits.maxToolCalls !== void 0) for (const threshold of crossedNoticeThresholds(toolCallsUsed, limits.maxToolCalls)) firedNotices.add(threshold);
10874
+ if (extension !== void 0 && limits.maxToolCalls !== void 0 && toolCallsUsed > limits.maxToolCalls) {
10875
+ extensionGrants = Math.min(extension.maxExtensions, Math.ceil((toolCallsUsed - limits.maxToolCalls) / extension.increment));
10876
+ extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
10877
+ }
10878
+ if (windowActive() !== void 0) {
10879
+ windowEntered = true;
10880
+ windowNoticeFired = true;
10881
+ }
10674
10882
  }
10675
10883
  const usageSlices = () => [...usageByPhaseModel.values()].map(({ role, servedBy: sliceServedBy, usage }) => ({
10676
10884
  servedBy: sliceServedBy,
@@ -10757,7 +10965,12 @@ async function runAgent(options) {
10757
10965
  };
10758
10966
  let terminalAdmitted = false;
10759
10967
  for (const [index, call] of calls.entries()) {
10760
- const expiredLimiter = limits.maxToolCalls !== void 0 && toolCallsUsed >= limits.maxToolCalls ? "maxToolCalls" : guard !== void 0 && guard.unitsExhausted() ? "toolUnits" : void 0;
10968
+ const expiryOf = () => {
10969
+ const cap = effectiveMaxToolCalls();
10970
+ return cap !== void 0 && toolCallsUsed >= cap ? "maxToolCalls" : guard !== void 0 && guard.unitsExhausted() ? "toolUnits" : void 0;
10971
+ };
10972
+ let expiredLimiter = expiryOf();
10973
+ if (expiredLimiter === "maxToolCalls" && call.name !== options.terminalTool?.name && tryToolBudgetGrant()) expiredLimiter = expiryOf();
10761
10974
  if (expiredLimiter !== void 0) {
10762
10975
  const tail = calls.slice(index);
10763
10976
  const terminalName = options.terminalTool?.name;
@@ -10944,6 +11157,27 @@ async function runAgent(options) {
10944
11157
  finished: finishArgs.result ?? null
10945
11158
  };
10946
11159
  }
11160
+ if (finalizationWindow !== void 0) {
11161
+ maybeMarkWindowEntry();
11162
+ let windowState = windowActive();
11163
+ if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
11164
+ if (windowState.budget === "tool calls" && extension !== void 0 && windowState.remaining + extension.increment > finalizationWindow.reserveCalls && tryToolBudgetGrant()) windowState = windowActive();
11165
+ if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
11166
+ events?.emit({
11167
+ type: "tool:end",
11168
+ toolName: gatedCall.name,
11169
+ outcome: "denied",
11170
+ durationMs: now() - gateStartedAt,
11171
+ guard: "finalization-window"
11172
+ });
11173
+ parts.push(errorPart(call, {
11174
+ error: finalizationWindowRefusalText(gatedCall.name, finalizationWindow.reserveCalls, windowState.budget),
11175
+ guard: "finalization-window"
11176
+ }));
11177
+ continue;
11178
+ }
11179
+ }
11180
+ }
10947
11181
  if (guard !== void 0) {
10948
11182
  const guardVerdict = guard.beforeExecute(gatedCall.name, gatedCall.args);
10949
11183
  if (guardVerdict.deny) {
@@ -10982,6 +11216,7 @@ async function runAgent(options) {
10982
11216
  };
10983
11217
  }
10984
11218
  }
11219
+ maybeMarkWindowEntry();
10985
11220
  return {
10986
11221
  parts,
10987
11222
  limitHit: false
@@ -11012,6 +11247,7 @@ async function runAgent(options) {
11012
11247
  await saveBoundary();
11013
11248
  } else if (limitHit) {
11014
11249
  status = "limit";
11250
+ if (limiter !== void 0) limitLimiter = limiter;
11015
11251
  if (guardTrip === true && guard !== void 0) {
11016
11252
  abortClass = "exploration";
11017
11253
  agentError = {
@@ -11031,6 +11267,8 @@ async function runAgent(options) {
11031
11267
  };
11032
11268
  }
11033
11269
  } else {
11270
+ flushExtensionNotices();
11271
+ flushWindowNotices();
11034
11272
  maybePushBudgetNotice();
11035
11273
  await saveBoundary();
11036
11274
  }
@@ -11474,6 +11712,7 @@ async function runAgent(options) {
11474
11712
  }
11475
11713
  if (limitHit) {
11476
11714
  status = "limit";
11715
+ if (limiter !== void 0) limitLimiter = limiter;
11477
11716
  if (guardTrip === true && guard !== void 0) {
11478
11717
  abortClass = "exploration";
11479
11718
  agentError = {
@@ -11494,6 +11733,8 @@ async function runAgent(options) {
11494
11733
  }
11495
11734
  break;
11496
11735
  }
11736
+ flushExtensionNotices();
11737
+ flushWindowNotices();
11497
11738
  maybePushBudgetNotice();
11498
11739
  if (options.summarize !== void 0 && !compactionDisabled && shouldCompact({
11499
11740
  lastTurnUsage,
@@ -11741,6 +11982,7 @@ async function runAgent(options) {
11741
11982
  });
11742
11983
  }
11743
11984
  if (reserveDispatch !== void 0) {
11985
+ reserveSummaryRan = true;
11744
11986
  const { outcome, target: reserveTarget } = reserveDispatch;
11745
11987
  servedBy = reserveTarget.resolved.ref;
11746
11988
  usageApprox = usageApprox || outcome.usageApprox;
@@ -12043,6 +12285,21 @@ async function runAgent(options) {
12043
12285
  if (abortClass !== void 0) result.abortClass = abortClass;
12044
12286
  if (errorMessage !== void 0) result.errorMessage = errorMessage;
12045
12287
  if (guard !== void 0) result.exploration = guard.summary(toolCallsUsed);
12288
+ if (limits.maxToolCalls !== void 0 || limits.toolUnits !== void 0 || extension !== void 0) {
12289
+ const toolBudget = { used: toolCallsUsed };
12290
+ const cap = effectiveMaxToolCalls();
12291
+ if (cap !== void 0) toolBudget.cap = cap;
12292
+ if (limits.toolUnits !== void 0) {
12293
+ toolBudget.unitsUsed = guard?.summary(toolCallsUsed).toolUnitsUsed ?? 0;
12294
+ toolBudget.unitsMax = limits.toolUnits.max;
12295
+ }
12296
+ if (extension !== void 0) toolBudget.extensionsGranted = extensionGrants;
12297
+ if (firedNotices.size > 0) toolBudget.noticesFired = [...firedNotices].sort((a, b) => a - b);
12298
+ if (reserveSummaryRan) toolBudget.finalizationReserveUsed = true;
12299
+ if (windowEntered) toolBudget.finalizationWindowEntered = true;
12300
+ if (limitLimiter !== void 0) toolBudget.limiter = limitLimiter;
12301
+ result.toolBudget = toolBudget;
12302
+ }
12046
12303
  if (limitPartial !== void 0) result.partial = limitPartial;
12047
12304
  const terminalName = options.terminalTool?.name;
12048
12305
  const schemaRejectedTerminalExchanges = terminalName === void 0 ? 0 : messages.reduce((count, message) => count + message.parts.filter((part) => part.type === "tool-result" && part.name === terminalName && part.isError === true && part.result?.error === terminalSchemaRejectionMessage(terminalName)).length, 0);
@@ -12449,17 +12706,28 @@ var RunBudget = class {
12449
12706
  * onUsage covers that hole), or when output is free. Zero or negative
12450
12707
  * means the turn cannot be dispatched within the budget.
12451
12708
  */
12452
- maxAffordableOutputTokens(servedBy, estimatedInputTokens, accountScope = "run") {
12453
- const pricing = this.pricingOf?.(servedBy);
12454
- if (pricing === void 0) return;
12455
- let remainingUsd;
12709
+ /**
12710
+ * The tightest chain headroom of `accountScope` in plain USD (RV301):
12711
+ * exactly the remaining money the output clamp below prices, before
12712
+ * any pricing. Undefined when every account on the chain is uncapped;
12713
+ * never negative. The tool budget extension admits a grant against
12714
+ * this number.
12715
+ */
12716
+ remainingUsd(accountScope = "run") {
12717
+ let remaining;
12456
12718
  for (const account of this.chainOf(accountScope)) {
12457
12719
  if (account.ceilingUsd === void 0) continue;
12458
12720
  const headroom = account.ceilingUsd - account.spentUsd - account.synthesisReserveUsd;
12459
- remainingUsd = remainingUsd === void 0 ? headroom : Math.min(remainingUsd, headroom);
12721
+ remaining = remaining === void 0 ? headroom : Math.min(remaining, headroom);
12460
12722
  }
12723
+ return remaining === void 0 ? void 0 : Math.max(0, remaining);
12724
+ }
12725
+ maxAffordableOutputTokens(servedBy, estimatedInputTokens, accountScope = "run") {
12726
+ const pricing = this.pricingOf?.(servedBy);
12727
+ if (pricing === void 0) return;
12728
+ const remainingUsd = this.remainingUsd(accountScope);
12461
12729
  if (remainingUsd === void 0) return;
12462
- return affordableOutputTokens(pricing, Math.max(0, remainingUsd), estimatedInputTokens);
12730
+ return affordableOutputTokens(pricing, remainingUsd, estimatedInputTokens);
12463
12731
  }
12464
12732
  /**
12465
12733
  * Live accounting; spend propagates from `accountScope` to every
@@ -14177,6 +14445,7 @@ function createCtx(internals, rootWorkflow) {
14177
14445
  budget: {
14178
14446
  beforeTurn: () => internals.budget.beforeTurn(budgetAccount),
14179
14447
  maxAffordableOutputTokens: (servedBy, estimatedInputTokens) => internals.budget.maxAffordableOutputTokens(servedBy, estimatedInputTokens, budgetAccount),
14448
+ remainingUsd: () => internals.budget.remainingUsd(budgetAccount),
14180
14449
  onUsage: (usage, servedBy) => internals.budget.onUsage(usage, servedBy, budgetAccount),
14181
14450
  signal: budgetAccount === "run" ? internals.budget.signal : AbortSignal.any([internals.budget.signal, internals.budget.signalOf(budgetAccount)].filter((signal) => signal !== void 0))
14182
14451
  },
@@ -14428,7 +14697,8 @@ function createCtx(internals, rootWorkflow) {
14428
14697
  entryRef: terminal.seq,
14429
14698
  ...resultUsageApprox ? { usageApprox: true } : {},
14430
14699
  ...result.transportRetries !== void 0 && result.transportRetries > 0 ? { retryCount: result.transportRetries } : {},
14431
- ...result.exploration === void 0 ? {} : { exploration: result.exploration }
14700
+ ...result.exploration === void 0 ? {} : { exploration: result.exploration },
14701
+ ...result.toolBudget === void 0 ? {} : { toolBudget: result.toolBudget }
14432
14702
  }, spanId);
14433
14703
  if (result.status === "escalated" && result.escalation !== void 0) {
14434
14704
  let decision = flavorBDecision;
@@ -17678,6 +17948,18 @@ function makeOrchestratorWorkflow(goal, opts) {
17678
17948
  * run falls back to the draft under a journaled decision and a warn
17679
17949
  * log, never silently.
17680
17950
  */
17951
+ /**
17952
+ * The reserve lifecycle snapshot (RV304 second half, the seventh
17953
+ * comparison experiment review, P1.7): configured is the declared
17954
+ * hold, held what actually registered on the cap account (zero when
17955
+ * no cap resolved and the config was silently inert), released
17956
+ * what the synthesis dispatch freed, remainingBeforeSynthesisUsd
17957
+ * the chain headroom the invocation saw right after the release,
17958
+ * and consumedUsd its own priced spend. Frozen into a journaled
17959
+ * decision at first completion, so a resume reports the identical
17960
+ * facts; absent everywhere when no reserve is configured.
17961
+ */
17962
+ let synthesisReserveLifecycle;
17681
17963
  const runSynthesis = async (draft) => {
17682
17964
  const spec = opts?.synthesis;
17683
17965
  if (spec === void 0) return draft;
@@ -17740,11 +18022,14 @@ function makeOrchestratorWorkflow(goal, opts) {
17740
18022
  ...repeatedClaims === void 0 ? {} : { repeatedClaims: repeatedClaims.length }
17741
18023
  }
17742
18024
  }, callingState.spanId);
18025
+ const configuredReserveUsd = opts?.budget?.synthesisReserveUsd ?? 0;
18026
+ const heldReserveUsd = orchestratorAccount === void 0 ? 0 : internals.budget.accountView(orchestratorAccount)?.synthesisReserveUsd ?? 0;
17743
18027
  const synthesisState = { ...callingState };
17744
18028
  if (orchestratorAccount !== void 0) {
17745
18029
  synthesisState.budgetScope = orchestratorAccount;
17746
18030
  internals.budget.releaseSynthesisReserve(orchestratorAccount);
17747
18031
  }
18032
+ const remainingBeforeSynthesisUsd = configuredReserveUsd > 0 ? internals.budget.remainingUsd(orchestratorAccount ?? callingState.budgetScope ?? void 0) : void 0;
17748
18033
  const synthesisBreak = validationSpec === void 0 ? void 0 : validationAbort.signal;
17749
18034
  if (synthesisBreak !== void 0) synthesisState.signal = callingState.signal === void 0 ? synthesisBreak : AbortSignal.any([callingState.signal, synthesisBreak]);
17750
18035
  const synthesisOpts = {
@@ -17767,6 +18052,46 @@ function makeOrchestratorWorkflow(goal, opts) {
17767
18052
  synthesisSchemaRejectedExchanges = synthesized.schemaRejectedTerminalExchanges ?? 0;
17768
18053
  synthesisSchemaRecoveredExchanges = synthesized.schemaRecoveredTerminalExchanges ?? 0;
17769
18054
  if (validationTermination !== void 0) throw validationTermination;
18055
+ if (configuredReserveUsd > 0) {
18056
+ const reserveKey = "synthesis-reserve-lifecycle";
18057
+ const prior = internals.replayer.snapshot().find((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.key === reserveKey);
18058
+ if (prior !== void 0) {
18059
+ const frozen = prior.value;
18060
+ synthesisReserveLifecycle = {
18061
+ configuredUsd: frozen.configuredUsd,
18062
+ heldUsd: frozen.heldUsd,
18063
+ releasedUsd: frozen.releasedUsd,
18064
+ ...frozen.remainingBeforeSynthesisUsd === void 0 ? {} : { remainingBeforeSynthesisUsd: frozen.remainingBeforeSynthesisUsd },
18065
+ ...frozen.consumedUsd === void 0 ? {} : { consumedUsd: frozen.consumedUsd }
18066
+ };
18067
+ } else {
18068
+ synthesisReserveLifecycle = {
18069
+ configuredUsd: configuredReserveUsd,
18070
+ heldUsd: heldReserveUsd,
18071
+ releasedUsd: heldReserveUsd,
18072
+ ...remainingBeforeSynthesisUsd === void 0 ? {} : { remainingBeforeSynthesisUsd },
18073
+ consumedUsd: synthesized.costUsd
18074
+ };
18075
+ await internals.replayer.appendSinglePhase({
18076
+ scope: callingState.scope,
18077
+ key: reserveKey,
18078
+ kind: "decision",
18079
+ status: "ok",
18080
+ spanId: internals.spans.mint(callingState.spanId),
18081
+ site: "orchestrator-synthesis-reserve",
18082
+ value: {
18083
+ decisionType: "orchestrator_synthesis_reserve",
18084
+ ...synthesisReserveLifecycle
18085
+ }
18086
+ });
18087
+ }
18088
+ internals.events.emit({
18089
+ type: "log",
18090
+ level: "info",
18091
+ msg: "orchestrator synthesis reserve lifecycle",
18092
+ data: { ...synthesisReserveLifecycle }
18093
+ }, callingState.spanId);
18094
+ }
17770
18095
  if (synthesized.status === "ok") return synthesized.output;
17771
18096
  if (validationSpec !== void 0) throw new FailRunError(`the synthesis invocation terminated with status '${synthesized.status}'` + (synthesized.errorMessage === void 0 ? "" : `: ${synthesized.errorMessage}`) + "; finish validators are configured, so the unvalidated draft cannot stand", { data: {
17772
18097
  source: "orchestrator_synthesis",
@@ -17977,7 +18302,8 @@ function makeOrchestratorWorkflow(goal, opts) {
17977
18302
  degradedReasons: decision.degradedReasons,
17978
18303
  ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
17979
18304
  ...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren },
17980
- ...envelopeSchemaRecovered === 0 ? {} : { schemaRecoveredFinishExchanges: envelopeSchemaRecovered }
18305
+ ...envelopeSchemaRecovered === 0 ? {} : { schemaRecoveredFinishExchanges: envelopeSchemaRecovered },
18306
+ ...synthesisReserveLifecycle === void 0 ? {} : { synthesisReserve: synthesisReserveLifecycle }
17981
18307
  };
17982
18308
  });
17983
18309
  }
@@ -18003,9 +18329,20 @@ function orchestrate(engine, goal, opts, runOptions) {
18003
18329
  * regardless of mix. A free tool (unit cost 0) lifts the units bound
18004
18330
  * entirely for calls of that tool.
18005
18331
  */
18332
+ /**
18333
+ * maxToolCalls at the fully extended cap (RV301): the projections
18334
+ * assume every grant lands, because quota demand and the checkpoint
18335
+ * loss window must hold at the worst case, not the base cap.
18336
+ */
18337
+ function extendedMaxToolCalls(limits) {
18338
+ if (limits.maxToolCalls === void 0) return;
18339
+ const extension = limits.toolBudgetExtension;
18340
+ return extension === void 0 ? limits.maxToolCalls : limits.maxToolCalls + extension.maxExtensions * extension.increment;
18341
+ }
18006
18342
  function overallExecutedCeiling(limits, toolCeilings) {
18007
18343
  const overall = toolCeilings.reduce((best, row) => row.ceiling === null ? best : best === null ? row.ceiling : Math.max(best, row.ceiling), null);
18008
- return limits.maxToolCalls !== void 0 && (overall === null || limits.maxToolCalls < overall) ? limits.maxToolCalls : overall;
18344
+ const callCap = extendedMaxToolCalls(limits);
18345
+ return callCap !== void 0 && (overall === null || callCap < overall) ? callCap : overall;
18009
18346
  }
18010
18347
  /**
18011
18348
  * The provider-call ceiling of one whole loop: maxTurns bounded by the
@@ -18054,9 +18391,10 @@ function toolCeilingsOf(limits) {
18054
18391
  ceiling: Math.floor(limits.toolUnits.max / cost)
18055
18392
  });
18056
18393
  }
18057
- if (limits.maxToolCalls !== void 0) terms.push({
18394
+ const callCap = extendedMaxToolCalls(limits);
18395
+ if (callCap !== void 0) terms.push({
18058
18396
  boundBy: "maxToolCalls",
18059
- ceiling: limits.maxToolCalls
18397
+ ceiling: callCap
18060
18398
  });
18061
18399
  if (terms.length === 0) {
18062
18400
  rows.push({
@@ -18193,6 +18531,8 @@ function preflightEstimate(input) {
18193
18531
  const waveGateInputs = [];
18194
18532
  const units = [];
18195
18533
  const shapes = [];
18534
+ /** Whether any declared spawn caps its tool budget (RV305). */
18535
+ let anyCappedSpawn = false;
18196
18536
  for (const spec of spawnSpecs) {
18197
18537
  const role = spec.role ?? "loop";
18198
18538
  const label = spec.label ?? role;
@@ -18289,6 +18629,21 @@ function preflightEstimate(input) {
18289
18629
  message: `spawn '${label}': the whole executed-call budget (${String(executedToolCallCeiling)} calls) fits into one parallel tool batch, and checkpoints write once per completed tool turn: the cap can be reached before any checkpoint exists, and a kill mid-batch re-pays every executed call on resume`,
18290
18630
  spawn: label
18291
18631
  });
18632
+ if (limits.toolBudgetExtension !== void 0) if (limits.maxToolCalls === void 0) say({
18633
+ severity: "warning",
18634
+ code: "inert-tool-budget-extension",
18635
+ message: `spawn '${label}' sets toolBudgetExtension without maxToolCalls: the extension only ever raises the executed-call cap, so there is nothing to extend`,
18636
+ spawn: label
18637
+ });
18638
+ else {
18639
+ const { increment, maxExtensions } = limits.toolBudgetExtension;
18640
+ say({
18641
+ severity: "info",
18642
+ code: "tool-budget-extension-exposure",
18643
+ message: `spawn '${label}': toolBudgetExtension can raise maxToolCalls ${String(limits.maxToolCalls)} by up to ${String(maxExtensions * increment)} extra calls (${String(maxExtensions)} grants of ${String(increment)}); every grant is admitted only under remaining budget headroom, and the projections already assume the fully extended cap`,
18644
+ spawn: label
18645
+ });
18646
+ }
18292
18647
  if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
18293
18648
  severity: "warning",
18294
18649
  code: "inert-finalization-reserve",
@@ -18301,6 +18656,37 @@ function preflightEstimate(input) {
18301
18656
  message: `spawn '${label}' sets toolBudgetNotices without maxToolCalls: the notices never fire`,
18302
18657
  spawn: label
18303
18658
  });
18659
+ if (limits.finalizationWindow !== void 0) if (limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
18660
+ severity: "warning",
18661
+ code: "inert-finalization-window",
18662
+ message: `spawn '${label}' sets finalizationWindow without maxToolCalls or toolUnits: no tool budget exists for the window to reserve a tail of`,
18663
+ spawn: label
18664
+ });
18665
+ else {
18666
+ const reserve = limits.finalizationWindow.reserveCalls;
18667
+ const windowCallCap = extendedMaxToolCalls(limits);
18668
+ const unitsMax = limits.toolUnits?.max;
18669
+ if (windowCallCap !== void 0 && reserve >= windowCallCap || unitsMax !== void 0 && reserve >= unitsMax) say({
18670
+ severity: "warning",
18671
+ code: "finalization-window-covers-cap",
18672
+ message: `spawn '${label}': finalizationWindow.reserveCalls ${String(reserve)} is not below the tool budget, so the window governs from the very first call and nothing but the allowlisted finalization tools ever executes`,
18673
+ spawn: label
18674
+ });
18675
+ if (limits.finalizationWindow.allow !== void 0 && limits.finalizationWindow.allow.length === 0) say({
18676
+ severity: "warning",
18677
+ code: "finalization-window-empty-allowlist",
18678
+ message: `spawn '${label}': finalizationWindow.allow is empty, so inside the window only the engine terminal tool (when one exists) remains callable and every other call is refused`,
18679
+ spawn: label
18680
+ });
18681
+ }
18682
+ const positiveCallCap = limits.maxToolCalls !== void 0 && limits.maxToolCalls > 0;
18683
+ if ((positiveCallCap || limits.toolUnits !== void 0) && limits.toolBudgetNotices !== true && limits.finalizationReserve === void 0 && limits.toolBudgetExtension === void 0 && limits.finalizationWindow === void 0) say({
18684
+ severity: "warning",
18685
+ code: "bare-tool-cap",
18686
+ message: `spawn '${label}' caps its tool budget (${positiveCallCap ? `maxToolCalls ${String(limits.maxToolCalls)}` : `toolUnits.max ${String(limits.toolUnits?.max ?? 0)}`}) with no softener: no toolBudgetNotices, no toolBudgetExtension, no finalizationReserve, no finalizationWindow. Expiry is a silent hard 'limit' the model never saw coming; enable a notice or a reserve, or drop the cap and rely on the USD ceiling`,
18687
+ spawn: label
18688
+ });
18689
+ if (positiveCallCap || limits.toolUnits !== void 0) anyCappedSpawn = true;
18304
18690
  if (unpriced && ceilingUsd !== void 0) say({
18305
18691
  severity: "warning",
18306
18692
  code: "unpriced-under-ceiling",
@@ -18360,6 +18746,12 @@ function preflightEstimate(input) {
18360
18746
  }
18361
18747
  let orchestratorReserveUsd;
18362
18748
  if (input.orchestrator !== void 0) {
18749
+ const acceptance = input.orchestrator.acceptance;
18750
+ if (acceptance !== void 0 && anyCappedSpawn && acceptance.acceptPartialChildren !== true && acceptance.acceptValidatedTerminalOutputOnLimit !== true) say({
18751
+ severity: "info",
18752
+ code: "capped-children-without-salvage",
18753
+ message: "children in the declared wave cap their tool budgets and the declared acceptance policy enables no salvage arm (acceptPartialChildren, acceptValidatedTerminalOutputOnLimit): a child that expires settles limit and counts against the policy with nothing to salvage"
18754
+ });
18363
18755
  const servedBy = resolveServing(defaults.routing?.orchestrate);
18364
18756
  if (servedBy === void 0) say({
18365
18757
  severity: "error",
@@ -19183,6 +19575,7 @@ function reduceInvocationTable(events) {
19183
19575
  row.costUsd = event.costUsd;
19184
19576
  row.usageApprox = event.usageApprox === true;
19185
19577
  row.retryCount = event.retryCount ?? 0;
19578
+ if (event.toolBudget !== void 0) row.toolBudget = event.toolBudget;
19186
19579
  totalCostUsd += event.costUsd;
19187
19580
  break;
19188
19581
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.85.0",
3
+ "version": "1.87.0",
4
4
  "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",