@rulvar/core 1.6.0 → 1.7.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
@@ -608,6 +608,23 @@ interface UsageSlice {
608
608
  usage: Usage;
609
609
  }
610
610
  /**
611
+ * Cost-attribution facts a live run knows at settlement and a pure
612
+ * journal fold cannot re-derive: the innermost phase name at the call
613
+ * site, the agent profile, the primary invocation role, the budget
614
+ * account the call debited, and whether the dispatch spent the
615
+ * orchestrator finalize reserve. Policy, never identity, exactly like
616
+ * usageByModel: none of it enters the content key, and entries written
617
+ * before the field shipped fold under the documented fallback buckets
618
+ * (empty phase, 'unknown' agent type, role 'loop').
619
+ */
620
+ interface CostAttributionFacts {
621
+ phase?: string;
622
+ agentType?: string;
623
+ role?: InvocationRole;
624
+ budgetAccount?: string;
625
+ finalizeReserve?: boolean;
626
+ }
627
+ /**
611
628
  * The per-model slices of a terminal entry: the recorded split when the
612
629
  * call spanned several models, else the whole usage attributed to
613
630
  * `servedBy`. The fallback is what makes every journal written before the
@@ -669,6 +686,13 @@ type JournalEntry = {
669
686
  * Policy, never identity: it does not enter the content key.
670
687
  */
671
688
  usageByModel?: UsageSlice[];
689
+ /**
690
+ * Terminal usage-bearing entries: the attribution facts behind the
691
+ * CostReport breakdowns, so a pure journal fold reproduces the live
692
+ * report byte for byte on replay. Policy, never identity, exactly
693
+ * like usageByModel.
694
+ */
695
+ costAttribution?: CostAttributionFacts;
672
696
  transcriptRef?: string;
673
697
  checkpointRef?: string;
674
698
  /**
@@ -2083,6 +2107,8 @@ interface TerminalPatch {
2083
2107
  servedBy?: ModelRef;
2084
2108
  /** Set only when the call spanned several serving models; see JournalEntry. */
2085
2109
  usageByModel?: UsageSlice[];
2110
+ /** Attribution facts behind the CostReport breakdowns; see JournalEntry. */
2111
+ costAttribution?: CostAttributionFacts;
2086
2112
  transcriptRef?: string;
2087
2113
  checkpointRef?: string;
2088
2114
  /** Terminal agent entries: Artifact list. */
@@ -3348,6 +3374,26 @@ interface BudgetAccountView {
3348
3374
  parentScope?: string;
3349
3375
  }
3350
3376
  /**
3377
+ * Why a ceiling error ended the work: the first closed account walking
3378
+ * from the debited scope toward the root, plus the root state, so the
3379
+ * outward message can name WHICH ceiling actually crossed instead of
3380
+ * blaming the run ceiling for every crossing.
3381
+ */
3382
+ interface BudgetExhaustionDiagnostics {
3383
+ crossed?: {
3384
+ scope: string;
3385
+ source: "root" | "orchestrator-cap" | "child-account";
3386
+ ceilingUsd: number;
3387
+ spentUsd: number;
3388
+ committedReserveUsd: number;
3389
+ finalizeReserveUsd: number;
3390
+ };
3391
+ root: {
3392
+ ceilingUsd?: number;
3393
+ spentUsd: number;
3394
+ };
3395
+ }
3396
+ /**
3351
3397
  * The per-run budget account tree. All spend accounting is per instance;
3352
3398
  * the journal remains the durable source (the root is seeded by the
3353
3399
  * ledger fold on resume, M2; sub-account reserves are recovered from
@@ -3395,7 +3441,20 @@ declare class RunBudget {
3395
3441
  parentScope?: string;
3396
3442
  ceilingUsd?: number;
3397
3443
  finalizeReserveUsd?: number;
3444
+ kind?: "orchestrator-cap";
3398
3445
  }): void;
3446
+ /**
3447
+ * The diagnostic projection behind a ceiling error: the first CLOSED
3448
+ * account (projected commitments included, exactly the layer-1
3449
+ * closure test) walking from `scope` toward the root, plus the root
3450
+ * state. 'run budget ceiling reached' under a healthy root misled the
3451
+ * v1.6.0 follow-up review's live probe when only a 0.18 USD
3452
+ * orchestrator cap had crossed under a 0.90 USD root; the message can
3453
+ * now name the account that actually ended the work. An unknown scope
3454
+ * degrades to root-only diagnostics instead of throwing: this runs on
3455
+ * the error path.
3456
+ */
3457
+ exhaustionDiagnostics(scope: string): BudgetExhaustionDiagnostics;
3399
3458
  accountView(scope: string): BudgetAccountView | undefined;
3400
3459
  /**
3401
3460
  * The admission remainder of one account: ceiling minus spend minus
@@ -4146,9 +4205,14 @@ type WorkflowEvent = {
4146
4205
  /** Folds the per-run attribution buckets into the normative CostReport. */
4147
4206
  declare function buildCostReport(attribution: CostAttribution, totalUsd: number): CostReport;
4148
4207
  /**
4149
- * The pure journal fold: byModel and totals from terminal entries, the
4150
- * same summation the kernel ledger uses (terminal usage exactly once,
4151
- * priced per servedBy, abandoned subtrees contribute zero).
4208
+ * The pure journal fold: the complete CostReport from terminal entries,
4209
+ * the same summation the kernel ledger uses (terminal usage exactly
4210
+ * once, priced per servedBy slice, abandoned subtrees contribute zero).
4211
+ * The orchestrator block folds too: spend attributed to the
4212
+ * orchestrator sub-account, the reserve-funded share of it, the armed
4213
+ * wake count, and the at-cap freeze flag from the journaled cap
4214
+ * decision, so a replay-only resume reproduces the block instead of
4215
+ * reading this process's live accounts (which a replay never charges).
4152
4216
  */
4153
4217
  declare function costReportFromJournal(entries: readonly JournalEntry[], priceUsd: (servedBy: ModelRef, usage: Usage) => number | undefined): CostReport;
4154
4218
  //#endregion
@@ -4171,7 +4235,15 @@ interface CostReport {
4171
4235
  byPhase: Record<string, number>;
4172
4236
  byAgentType: Record<string, number>;
4173
4237
  byRole: Record<InvocationRole, number>;
4174
- /** All-zero with forcedFinish false in runs without a dynamic orchestrator. */
4238
+ /**
4239
+ * All-zero with forcedFinish false in runs without a dynamic
4240
+ * orchestrator (or when no cap resolved, so no sub-account opened).
4241
+ * Folded purely from the journal: spentUsd is the priced usage of
4242
+ * entries debited to the orchestrator sub-account, reserveUsedUsd its
4243
+ * reserve-funded forced-finish share, wakes the ARMED (journaled)
4244
+ * wake suspensions (a wait satisfied synchronously never suspends and
4245
+ * is not counted), and forcedFinish the journaled at-cap decision.
4246
+ */
4175
4247
  orchestrator: {
4176
4248
  spentUsd: number; /** spentUsd / max(totalUsd, 0.01): the epsilon-floored H-OrchShare input. */
4177
4249
  share: number;
@@ -4852,6 +4924,13 @@ interface OrchestratorExtension {
4852
4924
  * machinery (reserves, freeze) completes in M7 (DEF-7).
4853
4925
  */
4854
4926
  interface OrchestratorBudgetSpec {
4927
+ /**
4928
+ * Absolute bound in USD. It never REPLACES the fraction bound:
4929
+ * effectiveCap = min(capUsd, (capFraction ?? 0.2) * ceiling), so an
4930
+ * explicit capUsd larger than the default fraction of the run ceiling
4931
+ * is still cut to that fraction (and a warn log says so). Pass
4932
+ * capFraction: 1.0 to make capUsd the sole bound.
4933
+ */
4855
4934
  capUsd?: number;
4856
4935
  /** default 0.2; effectiveCap = min of the given bounds */
4857
4936
  capFraction?: number;
@@ -6181,4 +6260,4 @@ interface SandboxBridge {
6181
6260
  }
6182
6261
  declare function createSandboxBridge(ctx: Ctx<never>, options: SandboxBridgeOptions): SandboxBridge;
6183
6262
  //#endregion
6184
- export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, BriefOpts, BudgetAccountView, BudgetDefaults, BudgetExhaustedError, 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, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, 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, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINISH_SCHEMA, FINISH_TOOL_NAME, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, 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_DEPTH_CEILING, MatchResult, McpConfig, MechanicalGateProfile, MechanicalGateVerdict, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type PricingTier, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, 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, 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, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, 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, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
6263
+ export { AWAIT_SCHEMA, AbandonAttempt, AbandonFold, AbandonPayload, AbandonedSpendView, AbortClass, type AdaptiveEvents, AdmissionController, AdmissionDecision, AdmissionRejectedError, AdmissionStatsBefore, AdmitLineage, AdmitRejectReason, AdmitSpec, AdmitVerdict, AgentCallError, AgentError, type AgentEvents, AgentIdentityInput, AgentOpts, AgentProfile, AgentProfilePermissions, AgentResult, AgentResultMeta, AgentStatus, ApproachSignatureInputs, ApprovalDecision, ApprovalIdentityInput, Artifact, AttemptOutcomeClass, BUDGET_ABORT_REASON, 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, ChildIdentityInput, type ClaimClass, type ClaimOp, type ClaimStatus, ClaimValidationOptions, CollectOpts, CollectedTurn, CompactionConfig, CompiledPermissionChain, CompiledWorkflow, ConfigError, type CoreEvents, CostAttribution, CostAttributionFacts, CostReport, CreateEngineOptions, Ctx, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, 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, DebitResult, DeclaredLadder, DedupIndex, DedupNote, DerivedKey, DeriverRegistry, DispositionRule, DispositionTable, DonorCandidate, DonorRef, DroppedItem, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EffectiveUsageLimits, Effort, Engine, EngineDefaults, EntryKind, EntryRef, EntryStatus, ErrorClass, ErrorCode, ErrorPolicy, EscalatedResult, EscalationDecision, EscalationDigest, EscalationKind, EscalationLimits, EscalationOptions, EscalationReport, EscalationRequest, EventBus, type EvidenceRef, ExtensionAppendInput, ExtensionDispatchSpec, ExternalIdentityInput, ExternalRegistry, ExtractNecessityInput, FINISH_SCHEMA, FINISH_TOOL_NAME, FailoverTarget, FailoverTrigger, FallbackField, FallbackTrigger, FileModelKnowledgeStore, FileModelKnowledgeStoreOptions, FileTranscriptStore, FinishInfo, Gate, GateAudit, type GateRecord, GitWorktreeProvider, GitWorktreeProviderOptions, GraftBoot, HashVersion, HookVerdict, INBOX_PROPOSAL_TTL_DAYS, IdentityInput, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, InvocationRole, type IsolationProvider, type IsolationSpec, Issue$1 as Issue, JournalCompatSubCode, JournalCompatibilityError, JournalEntry, JournalMatcher, JournalMissError, JournalOperation, JournalOrderViolation, 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_DEPTH_CEILING, MatchResult, McpConfig, MechanicalGateProfile, MechanicalGateVerdict, type ModelCaps, ModelChoice, type ModelClaim, ModelEpochInputs, type ModelKnowledgeHandle, type ModelKnowledgeStore, ModelListConstraint, ModelRef, ModelRetry, ModelSpec, Msg, NoProgressDetector, NodeId, NodeLinkValue, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OnEscalation, OperationDisposition, OrchestrateOptions, OrchestratorBudgetSpec, OrchestratorCapConfigError, OrchestratorExtension, OrchestratorExtensionIO, OrchestratorRuntime, Out, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, Part, PendingExternal, PendingToolTurn, PermissionConfig, PermissionGate, PermissionHook, PermissionPreset, PermissionRule, PermissionVerdict, PhaseTarget, PipelineCollected, PipelineOpts, PlanInvariantError, PriceTable, PricedUsage, type Pricing, type PricingTier, type ProviderAdapter, QualityFloors, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RandIdentityInput, RandPayload, RefEntryAppender, RefEntryClassification, RefusalInfo, ReplayDisposition, ReplayMode, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionAttempt, ResolutionBy, ResolutionFold, ResolutionLayer, ResolutionOutcome, ResolutionPayload, ResolvedInvocation, ResolvedToolset, ResumeHandle, ResumeOptions, ResumePreview, ResumeReport, RetryClass, RetryPolicy, ReuseConfig, RiskRuleValue, Role, RulvarError, RulvarErrorCode, RunAgentOptions, RunBudget, RunEventSink, type RunFilter, RunHandle, RunInternals, type RunMeta, RunOptions, RunOutcome, RunProfile, RunStatus, RuntimeEventSink, SPAWN_AGENT_SCHEMA, SandboxBridge, SandboxBridgeOptions, SandboxError, SandboxHostToWorker, SandboxMethod, SandboxWorkerToHost, SchemaPair, SchemaSpec, SchemaValidationResult, ScopeSegment, ScriptRejected, ScriptRunner, ScrubNote, Semaphore, SerializationHook, Settled, 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, 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, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, atCompactionThreshold, 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, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readTerminationInit, registryKeyRing, remeasureQueue, replayDisposition, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateSchemaSpec, validateTerminationLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
package/dist/index.js CHANGED
@@ -5484,6 +5484,7 @@ var Replayer = class {
5484
5484
  if (patch.usageApprox !== void 0) entry.usageApprox = patch.usageApprox;
5485
5485
  if (patch.servedBy !== void 0) entry.servedBy = patch.servedBy;
5486
5486
  if (patch.usageByModel !== void 0) entry.usageByModel = patch.usageByModel;
5487
+ if (patch.costAttribution !== void 0) entry.costAttribution = patch.costAttribution;
5487
5488
  if (patch.transcriptRef !== void 0) entry.transcriptRef = patch.transcriptRef;
5488
5489
  if (patch.checkpointRef !== void 0) entry.checkpointRef = patch.checkpointRef;
5489
5490
  if (patch.artifacts !== void 0) entry.artifacts = toJournalValue(patch.artifacts, "terminal artifacts");
@@ -6147,19 +6148,28 @@ var FileTranscriptStore = class {
6147
6148
  //#endregion
6148
6149
  //#region src/engine/cost-report.ts
6149
6150
  /**
6150
- * CostReport builders (M5-T03). Two
6151
- * sources, one shape:
6151
+ * CostReport builders (M5-T03; follow-up: one pure fold).
6152
6152
  *
6153
- * - `buildCostReport` folds the LIVE per-run attribution buckets (ctx
6154
- * accumulates byModel/byPhase/byAgentType/byRole per call) around the
6155
- * ledger-fold total, so report totals equal the budget ledger fold
6156
- * totals exactly at settle.
6157
- * - `costReportFromJournal` is the pure journal fold for STORED runs
6158
- * (shells, `rulvar inspect`): terminal usage priced per servedBy with
6159
- * abandoned subtrees contributing zero, exactly like the kernel's
6160
- * ledger fold. Phase, agentType, and role attribution are live-run
6161
- * facts that entries do not carry, so those buckets are empty here;
6162
- * byRole and the orchestrator block complete in M7 (DEF-7).
6153
+ * `costReportFromJournal` is THE report: a pure fold over terminal
6154
+ * entries that both the engine's settle path and stored-run inspection
6155
+ * (shells, `rulvar inspect`) use, so a replayed run reports the same
6156
+ * numbers byte for byte. Terminal entries carry their attribution facts
6157
+ * (`costAttribution`: phase, agent type, primary role, budget account,
6158
+ * finalize-reserve flag) exactly so this fold can reproduce every
6159
+ * breakdown without live state; entries written before the facts
6160
+ * shipped fold under the documented fallbacks (empty phase, 'unknown'
6161
+ * agent type, role 'loop').
6162
+ *
6163
+ * Inclusion policy, applied to the total and EVERY breakdown alike:
6164
+ * terminal usage exactly once, priced per serving slice, entries under
6165
+ * abandoned subtrees contribute zero (their spend is tracked separately
6166
+ * in the abandoned-spend ledger the orchestrator sees). Attempts that
6167
+ * were paid but never abandoned (a cancelled root attempt, a dangling
6168
+ * child) are real spend and stay included everywhere.
6169
+ *
6170
+ * `buildCostReport` folds the LIVE per-run attribution buckets around
6171
+ * the ledger total; it remains for hosts that accumulated their own
6172
+ * `CostAttribution`, but the engine no longer builds outcomes from it.
6163
6173
  *
6164
6174
  * Unpriced models surface in `unpriced`, never as a silent zero.
6165
6175
  */
@@ -6174,14 +6184,9 @@ const ROLES = [
6174
6184
  function emptyByRole() {
6175
6185
  return Object.fromEntries(ROLES.map((role) => [role, 0]));
6176
6186
  }
6177
- function zeroOrchestrator() {
6178
- return {
6179
- spentUsd: 0,
6180
- share: 0,
6181
- wakes: 0,
6182
- forcedFinish: false,
6183
- reserveUsedUsd: 0
6184
- };
6187
+ /** The orchestrator sub-account naming rule of makeOrchestratorWorkflow. */
6188
+ function isOrchestratorAccount(scope) {
6189
+ return scope === "orchestrator" || scope.endsWith("/orchestrator");
6185
6190
  }
6186
6191
  /** Folds the per-run attribution buckets into the normative CostReport. */
6187
6192
  function buildCostReport(attribution, totalUsd) {
@@ -6207,16 +6212,30 @@ function buildCostReport(attribution, totalUsd) {
6207
6212
  };
6208
6213
  }
6209
6214
  /**
6210
- * The pure journal fold: byModel and totals from terminal entries, the
6211
- * same summation the kernel ledger uses (terminal usage exactly once,
6212
- * priced per servedBy, abandoned subtrees contribute zero).
6215
+ * The pure journal fold: the complete CostReport from terminal entries,
6216
+ * the same summation the kernel ledger uses (terminal usage exactly
6217
+ * once, priced per servedBy slice, abandoned subtrees contribute zero).
6218
+ * The orchestrator block folds too: spend attributed to the
6219
+ * orchestrator sub-account, the reserve-funded share of it, the armed
6220
+ * wake count, and the at-cap freeze flag from the journaled cap
6221
+ * decision, so a replay-only resume reproduces the block instead of
6222
+ * reading this process's live accounts (which a replay never charges).
6213
6223
  */
6214
6224
  function costReportFromJournal(entries, priceUsd) {
6215
6225
  const abandonFold = buildAbandonFold(entries);
6216
6226
  const byModel = {};
6227
+ const byPhase = {};
6228
+ const byAgentType = {};
6229
+ const byRole = emptyByRole();
6217
6230
  const unpriced = [];
6218
6231
  let totalUsd = 0;
6232
+ let orchestratorSpentUsd = 0;
6233
+ let reserveUsedUsd = 0;
6234
+ let wakes = 0;
6235
+ let forcedFinish = false;
6219
6236
  for (const entry of entries) {
6237
+ if (entry.kind === "decision" && entry.value?.decisionType === "orchestrator_budget_cap") forcedFinish = true;
6238
+ if (entry.kind === "external" && entry.status === "suspended" && typeof entry.value?.key === "string" && (entry.value.key.startsWith("wake:") || entry.value.key.includes(":wake:"))) wakes += 1;
6220
6239
  if (entry.kind !== "resolution" && entry.kind !== "abandon" && abandonFold.isAbandoned(entry.ref ?? entry.seq)) continue;
6221
6240
  if (entry.status === "running" || entry.usage === void 0) continue;
6222
6241
  const priced = priceEntryUsage(entry, priceUsd);
@@ -6226,14 +6245,30 @@ function costReportFromJournal(entries, priceUsd) {
6226
6245
  });
6227
6246
  for (const slice of priced.priced) byModel[slice.servedBy] = (byModel[slice.servedBy] ?? 0) + slice.usd;
6228
6247
  totalUsd += priced.usd;
6248
+ const facts = entry.costAttribution;
6249
+ const phase = facts?.phase ?? "";
6250
+ byPhase[phase] = (byPhase[phase] ?? 0) + priced.usd;
6251
+ const agentType = facts?.agentType ?? "unknown";
6252
+ byAgentType[agentType] = (byAgentType[agentType] ?? 0) + priced.usd;
6253
+ byRole[facts?.role ?? "loop"] += priced.usd;
6254
+ if (facts?.budgetAccount !== void 0 && isOrchestratorAccount(facts.budgetAccount)) {
6255
+ orchestratorSpentUsd += priced.usd;
6256
+ if (facts.finalizeReserve === true) reserveUsedUsd += priced.usd;
6257
+ }
6229
6258
  }
6230
6259
  return {
6231
6260
  totalUsd,
6232
6261
  byModel,
6233
- byPhase: {},
6234
- byAgentType: {},
6235
- byRole: emptyByRole(),
6236
- orchestrator: zeroOrchestrator(),
6262
+ byPhase,
6263
+ byAgentType,
6264
+ byRole,
6265
+ orchestrator: {
6266
+ spentUsd: orchestratorSpentUsd,
6267
+ share: orchestratorSpentUsd / Math.max(totalUsd, .01),
6268
+ wakes,
6269
+ forcedFinish,
6270
+ reserveUsedUsd
6271
+ },
6237
6272
  unpriced
6238
6273
  };
6239
6274
  }
@@ -8510,6 +8545,28 @@ async function runAgent(options) {
8510
8545
  await saveBoundary();
8511
8546
  continue loop;
8512
8547
  }
8548
+ if (options.terminalTool !== void 0) {
8549
+ noProgress.recordTurn({ toolCalls: 0 });
8550
+ if (noProgress.tripped) {
8551
+ status = "limit";
8552
+ abortClass = "no-progress";
8553
+ agentError = {
8554
+ kind: "terminal",
8555
+ retryable: false
8556
+ };
8557
+ errorMessage = noProgress.describe();
8558
+ break;
8559
+ }
8560
+ messages.push({
8561
+ role: "user",
8562
+ parts: [{
8563
+ type: "text",
8564
+ text: outcome.finish?.reason === "max-tokens" ? `The turn was cut at the output token limit before any tool call. Be brief and call the '${options.terminalTool.name}' tool now; plain text is not a valid completion.` : `The turn ended without a tool call. Call the '${options.terminalTool.name}' tool to complete; plain text is not a valid completion.`
8565
+ }]
8566
+ });
8567
+ await saveBoundary();
8568
+ continue loop;
8569
+ }
8513
8570
  if (options.schema === void 0) {
8514
8571
  output = outcome.turn.text;
8515
8572
  break;
@@ -8942,8 +8999,43 @@ var RunBudget = class {
8942
8999
  controller: new AbortController()
8943
9000
  };
8944
9001
  if (options.ceilingUsd !== void 0) account.ceilingUsd = options.ceilingUsd;
9002
+ if (options.kind !== void 0) account.kind = options.kind;
8945
9003
  this.accounts.set(scope, account);
8946
9004
  }
9005
+ /**
9006
+ * The diagnostic projection behind a ceiling error: the first CLOSED
9007
+ * account (projected commitments included, exactly the layer-1
9008
+ * closure test) walking from `scope` toward the root, plus the root
9009
+ * state. 'run budget ceiling reached' under a healthy root misled the
9010
+ * v1.6.0 follow-up review's live probe when only a 0.18 USD
9011
+ * orchestrator cap had crossed under a 0.90 USD root; the message can
9012
+ * now name the account that actually ended the work. An unknown scope
9013
+ * degrades to root-only diagnostics instead of throwing: this runs on
9014
+ * the error path.
9015
+ */
9016
+ exhaustionDiagnostics(scope) {
9017
+ let chain;
9018
+ try {
9019
+ chain = this.chainOf(scope);
9020
+ } catch {
9021
+ chain = [this.root];
9022
+ }
9023
+ const crossed = chain.find((account) => account.ceilingUsd !== void 0 && account.spentUsd + account.committedReserveUsd + account.finalizeReserveUsd >= account.ceilingUsd);
9024
+ const root = this.root;
9025
+ const diagnostics = { root: {
9026
+ spentUsd: root.spentUsd,
9027
+ ...root.ceilingUsd === void 0 ? {} : { ceilingUsd: root.ceilingUsd }
9028
+ } };
9029
+ if (crossed?.ceilingUsd !== void 0) diagnostics.crossed = {
9030
+ scope: crossed.scope,
9031
+ source: crossed.scope === "run" ? "root" : crossed.kind === "orchestrator-cap" ? "orchestrator-cap" : "child-account",
9032
+ ceilingUsd: crossed.ceilingUsd,
9033
+ spentUsd: crossed.spentUsd,
9034
+ committedReserveUsd: crossed.committedReserveUsd,
9035
+ finalizeReserveUsd: crossed.finalizeReserveUsd
9036
+ };
9037
+ return diagnostics;
9038
+ }
8947
9039
  accountView(scope) {
8948
9040
  const account = this.accounts.get(scope);
8949
9041
  if (account === void 0) return;
@@ -9791,6 +9883,13 @@ const kTerminalTool = Symbol("rulvar.terminalTool");
9791
9883
  * graft boot). Dangling redispatch checkpoints take precedence.
9792
9884
  */
9793
9885
  const kBootCheckpoint = Symbol("rulvar.bootCheckpoint");
9886
+ /**
9887
+ * Internal AgentOpts channel: marks the orchestrator forced-finish
9888
+ * dispatch, whose spend draws from the released finalize reserve
9889
+ * (DEF-7). Settlement stamps the flag into the terminal's cost
9890
+ * attribution so the journal fold reproduces reserveUsedUsd.
9891
+ */
9892
+ const kFinalizeReserve = Symbol("rulvar.finalizeReserve");
9794
9893
  /** Typed accessor used by the in-package consumers. */
9795
9894
  function runtimeOf(ctx) {
9796
9895
  const runtime = ctxRuntimes.get(ctx);
@@ -10653,6 +10752,13 @@ function createCtx(internals, rootWorkflow) {
10653
10752
  usage: result.usage,
10654
10753
  servedBy: result.servedBy,
10655
10754
  ...result.usageByModel === void 0 ? {} : { usageByModel: result.usageByModel },
10755
+ costAttribution: {
10756
+ ...state.phase === void 0 ? {} : { phase: state.phase },
10757
+ agentType,
10758
+ role: primaryRole,
10759
+ budgetAccount: state.budgetScope ?? "run",
10760
+ ...opts[kFinalizeReserve] === true ? { finalizeReserve: true } : {}
10761
+ },
10656
10762
  transcriptRef: result.transcriptRef
10657
10763
  };
10658
10764
  if (result.status === "escalated" && result.escalation !== void 0) terminalPatch.escalation = result.escalation;
@@ -10720,10 +10826,25 @@ function createCtx(internals, rootWorkflow) {
10720
10826
  bump(internals.cost.byPhase, state.phase ?? "", usd);
10721
10827
  bump(internals.cost.byAgentType, agentType, usd);
10722
10828
  internals.cost.byRole.set(primaryRole, (internals.cost.byRole.get(primaryRole) ?? 0) + usd);
10723
- if (result.error?.kind === "budget" || internals.budget.exhausted && result.status !== "ok") throw new BudgetExhaustedError("run budget ceiling reached during agent execution", { data: {
10724
- scope: state.scope,
10725
- entryRef: terminal.seq
10726
- } });
10829
+ if (result.error?.kind === "budget" || internals.budget.exhausted && result.status !== "ok") {
10830
+ const diagnostics = internals.budget.exhaustionDiagnostics(state.budgetScope ?? "run");
10831
+ const crossed = diagnostics.crossed;
10832
+ const rootSuffix = `run root: spent ${diagnostics.root.spentUsd.toFixed(4)}` + (diagnostics.root.ceilingUsd === void 0 ? " USD, no ceiling" : ` of ${diagnostics.root.ceilingUsd.toFixed(4)} USD`);
10833
+ throw new BudgetExhaustedError(crossed === void 0 || crossed.source === "root" ? "run budget ceiling reached during agent execution" : (crossed.source === "orchestrator-cap" ? "orchestrator budget cap reached during agent execution" : "budget sub-account ceiling reached during agent execution") + ` (account '${crossed.scope}': spent ${crossed.spentUsd.toFixed(4)}` + (crossed.committedReserveUsd + crossed.finalizeReserveUsd > 0 ? ` plus ${(crossed.committedReserveUsd + crossed.finalizeReserveUsd).toFixed(4)} reserved` : "") + ` of ${crossed.ceilingUsd.toFixed(4)} USD; ${rootSuffix})`, { data: {
10834
+ scope: state.scope,
10835
+ entryRef: terminal.seq,
10836
+ source: crossed?.source ?? "root",
10837
+ rootSpentUsd: diagnostics.root.spentUsd,
10838
+ ...diagnostics.root.ceilingUsd === void 0 ? {} : { rootCeilingUsd: diagnostics.root.ceilingUsd },
10839
+ ...crossed === void 0 ? {} : {
10840
+ crossedScope: crossed.scope,
10841
+ crossedCeilingUsd: crossed.ceilingUsd,
10842
+ crossedSpentUsd: crossed.spentUsd,
10843
+ crossedCommittedReserveUsd: crossed.committedReserveUsd,
10844
+ crossedFinalizeReserveUsd: crossed.finalizeReserveUsd
10845
+ }
10846
+ } });
10847
+ }
10727
10848
  if (opts.fallback !== void 0) {
10728
10849
  const trigger = fallbackTriggerOf(result);
10729
10850
  if (trigger !== void 0 && opts.fallback.on.includes(trigger)) return runFallbackAttempt(running.seq, trigger, spanId);
@@ -11313,10 +11434,16 @@ function makeOrchestratorWorkflow(goal, opts) {
11313
11434
  const finalizeTurns = spec?.finalizeTurns ?? 2;
11314
11435
  const finalizeReserveUsd = spec?.finalizeReserveUsd ?? finalizeTurns * turnEstimateUsd;
11315
11436
  if (extension !== void 0 && effectiveCapUsd < finalizeReserveUsd) throw new OrchestratorCapConfigError(`effectiveCap ${effectiveCapUsd.toFixed(4)} USD is below the finalize reserve ${finalizeReserveUsd.toFixed(4)} USD`);
11437
+ if (spec?.capUsd !== void 0 && spec.capFraction === void 0 && effectiveCapUsd < spec.capUsd) internals.events.emit({
11438
+ type: "log",
11439
+ level: "warn",
11440
+ msg: `orchestrator budget.capUsd ${spec.capUsd.toFixed(4)} USD is bounded to ${effectiveCapUsd.toFixed(4)} USD by the default capFraction 0.2 of the run ceiling (effectiveCap = min(capUsd, capFraction * ceiling)); pass capFraction: 1.0 to make capUsd the sole bound`
11441
+ }, callingState.spanId);
11316
11442
  orchestratorAccount = callingState.scope === "" ? "orchestrator" : `${callingState.scope}/orchestrator`;
11317
11443
  internals.budget.openAccount(orchestratorAccount, {
11318
11444
  parentScope: callingState.budgetScope ?? "run",
11319
- ceilingUsd: effectiveCapUsd
11445
+ ceilingUsd: effectiveCapUsd,
11446
+ kind: "orchestrator-cap"
11320
11447
  });
11321
11448
  if (extension !== void 0) internals.budget.commitFinalizeReserve(orchestratorAccount, finalizeReserveUsd);
11322
11449
  capState = {
@@ -11332,6 +11459,15 @@ function makeOrchestratorWorkflow(goal, opts) {
11332
11459
  const records = /* @__PURE__ */ new Map();
11333
11460
  const byOrdinal = /* @__PURE__ */ new Map();
11334
11461
  const rejectedByOrdinal = /* @__PURE__ */ new Map();
11462
+ /**
11463
+ * The journaled spec behind each recovered ordinal: the idempotent
11464
+ * re-execution guard compares it against the incoming call, because
11465
+ * after a cross-attempt resume a REGENERATED turn (the boundary
11466
+ * checkpoint predates the lost turn) may decide differently, and
11467
+ * handing it the prior ordinal's handle would bind the transcript
11468
+ * to a stranger's child.
11469
+ */
11470
+ const recoveredSpecByOrdinal = /* @__PURE__ */ new Map();
11335
11471
  let nextOrdinal = 0;
11336
11472
  let orchSeq;
11337
11473
  const deliveredNodeIds = /* @__PURE__ */ new Set();
@@ -11367,7 +11503,7 @@ function makeOrchestratorWorkflow(goal, opts) {
11367
11503
  const controller = new AbortController();
11368
11504
  const upstream = callingState.signal ?? internals.runSignal;
11369
11505
  const scope = placement?.childScope ?? childScopeOf();
11370
- if (placement !== void 0) internals.budget.openAccount(scope, {
11506
+ if (placement?.ownAccount === true) internals.budget.openAccount(scope, {
11371
11507
  parentScope: callingState.budgetScope ?? "run",
11372
11508
  ...placement.childCeilingUsd === void 0 ? {} : { ceilingUsd: placement.childCeilingUsd }
11373
11509
  });
@@ -11375,7 +11511,7 @@ function makeOrchestratorWorkflow(goal, opts) {
11375
11511
  scope,
11376
11512
  spanId: internals.spans.mint(callingState.spanId),
11377
11513
  signal: upstream === void 0 ? controller.signal : AbortSignal.any([upstream, controller.signal]),
11378
- budgetScope: placement !== void 0 ? scope : callingState.budgetScope ?? "run"
11514
+ budgetScope: placement?.ownAccount === true ? scope : callingState.budgetScope ?? "run"
11379
11515
  };
11380
11516
  let resolveHandle = () => void 0;
11381
11517
  const handlePromise = new Promise((resolve) => {
@@ -11457,6 +11593,7 @@ function makeOrchestratorWorkflow(goal, opts) {
11457
11593
  nextOrdinal += 1;
11458
11594
  return { handle: (await dispatchChild(spec, spawnOrdinal, identity, {
11459
11595
  childScope,
11596
+ ownAccount: true,
11460
11597
  ...spec.budgetUsd === void 0 ? {} : { childCeilingUsd: spec.budgetUsd }
11461
11598
  })).handle };
11462
11599
  },
@@ -11487,33 +11624,61 @@ function makeOrchestratorWorkflow(goal, opts) {
11487
11624
  handle
11488
11625
  };
11489
11626
  };
11490
- /** Rebuilds spawn records from the journal (the crash-resume contract). */
11627
+ /**
11628
+ * True when `scope` is a root-attempt scope of THIS orchestration:
11629
+ * agentScope(callingState.scope, n) for some dispatch seq n. Nested
11630
+ * orchestrations live under their own wf: child scopes and never
11631
+ * match a foreign calling scope.
11632
+ */
11633
+ const scopeOfThisOrchestration = (scope) => {
11634
+ const prefix = callingState.scope === "" ? "" : `${callingState.scope}/`;
11635
+ return scope.startsWith(prefix) && /^agent:\d+$/.test(scope.slice(prefix.length));
11636
+ };
11637
+ /**
11638
+ * Rebuilds spawn records from the journal (the crash-resume
11639
+ * contract). Recovery is ORCHESTRATION-scoped, not attempt-scoped:
11640
+ * decisions journal at the orchestrate call's own scope, which is
11641
+ * stable across root attempts, so a rerun after a cancelled root
11642
+ * (the budget-abort shape the v1.6.0 follow-up review resumed) sees
11643
+ * every prior decision instead of re-deciding and re-paying.
11644
+ * Recovered children re-dispatch PINNED to their journaled child
11645
+ * scope: settled ones forward-match and replay for free, a dangling
11646
+ * one redispatches live (at-least-once), and a decision without a
11647
+ * dispatch entry rolls forward to a fresh dispatch.
11648
+ */
11491
11649
  const recover = async () => {
11492
- const scope = childScopeOf();
11650
+ const currentScope = childScopeOf();
11493
11651
  const admissions = internals.replayer.snapshot().filter((entry) => {
11494
- if (entry.kind !== "decision") return false;
11652
+ if (entry.kind !== "decision" || entry.scope !== callingState.scope) return false;
11495
11653
  const value = entry.value;
11496
- return value?.decisionType === "spawn-admission" && (value.origin === "spawn_agent" || value.origin === "parallel_agents") && value.orchestratorScope === scope;
11654
+ return value?.decisionType === "spawn-admission" && (value.origin === "spawn_agent" || value.origin === "parallel_agents");
11497
11655
  }).map((entry) => entry.value).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
11498
11656
  for (const value of admissions) {
11499
11657
  nextOrdinal = Math.max(nextOrdinal, value.spawnOrdinal + 1);
11500
11658
  const decision = value.decision;
11659
+ recoveredSpecByOrdinal.set(value.spawnOrdinal, value.spec);
11501
11660
  if (decision.verdict.kind !== "admit") {
11502
11661
  rejectedByOrdinal.set(value.spawnOrdinal, decision);
11503
11662
  continue;
11504
11663
  }
11505
- admission.recoverChild(scope);
11506
- await dispatchChild(value.spec, value.spawnOrdinal, {
11664
+ admission.recoverChild(currentScope);
11665
+ const childScope = value.childScope ?? value.orchestratorScope;
11666
+ const record = await dispatchChild(value.spec, value.spawnOrdinal, {
11507
11667
  nodeId: decision.nodeId ?? "unknown",
11508
11668
  logicalTaskId: decision.verdict.lineage.logicalTaskId
11509
- });
11669
+ }, { childScope });
11670
+ const dispatched = internals.replayer.snapshot().find((entry) => entry.seq === record.handle);
11671
+ if (dispatched !== void 0) {
11672
+ for (const prior of internals.replayer.snapshot()) if (prior.kind === "agent" && prior.status === "running" && prior.seq !== record.handle && prior.scope === dispatched.scope && prior.key === dispatched.key && prior.ordinal === dispatched.ordinal && !records.has(prior.seq)) records.set(prior.seq, record);
11673
+ }
11510
11674
  }
11511
- const wakePrefix = `wake:${String(orchSeq ?? -1)}:`;
11512
11675
  for (const entry of internals.replayer.snapshot()) {
11513
11676
  if (entry.status !== "suspended" || entry.kind !== "external") continue;
11677
+ if (!scopeOfThisOrchestration(entry.scope)) continue;
11514
11678
  const payload = entry.value;
11515
- if (typeof payload?.key !== "string" || !payload.key.startsWith(wakePrefix)) continue;
11516
- wakeOrdinal = Math.max(wakeOrdinal, Number(payload.key.slice(wakePrefix.length)) + 1);
11679
+ const match = typeof payload?.key === "string" ? /^wake:\d+:(\d+)$/.exec(payload.key) : null;
11680
+ if (match === null) continue;
11681
+ wakeOrdinal = Math.max(wakeOrdinal, Number(match[1]) + 1);
11517
11682
  const suspension = internals.replayer.suspensionState(entry.seq);
11518
11683
  if (suspension.state === "resolved") markDelivered(suspension.value);
11519
11684
  }
@@ -11641,10 +11806,12 @@ function makeOrchestratorWorkflow(goal, opts) {
11641
11806
  await recoveryDone;
11642
11807
  const spawnOrdinal = nextOrdinal;
11643
11808
  nextOrdinal += 1;
11809
+ const priorSpec = recoveredSpecByOrdinal.get(spawnOrdinal);
11810
+ const specMatches = priorSpec === void 0 || priorSpec.agentType === params.agentType && priorSpec.prompt === params.prompt;
11644
11811
  const recovered = byOrdinal.get(spawnOrdinal);
11645
- if (recovered !== void 0) return { handle: recovered.handle };
11812
+ if (recovered !== void 0 && specMatches) return { handle: recovered.handle };
11646
11813
  const recoveredRejection = rejectedByOrdinal.get(spawnOrdinal);
11647
- if (recoveredRejection !== void 0) throw new AdmissionRejectedError(`admission rejected spawn ordinal ${String(spawnOrdinal)} (recovered verdict)`, { data: { decision: recoveredRejection } });
11814
+ if (recoveredRejection !== void 0 && specMatches) throw new AdmissionRejectedError(`admission rejected spawn ordinal ${String(spawnOrdinal)} (recovered verdict)`, { data: { decision: recoveredRejection } });
11648
11815
  if (opts?.maxSpawns !== void 0 && spawnOrdinal >= opts.maxSpawns) {
11649
11816
  internals.events.emit({
11650
11817
  type: "spawn:rejected",
@@ -11902,7 +12069,11 @@ function makeOrchestratorWorkflow(goal, opts) {
11902
12069
  orchSeq = seq;
11903
12070
  recover().then(releaseRecovery, releaseRecovery);
11904
12071
  },
11905
- [kTerminalTool]: { name: FINISH_TOOL_NAME }
12072
+ [kTerminalTool]: { name: FINISH_TOOL_NAME },
12073
+ ...(() => {
12074
+ const priorCancelledRoot = internals.replayer.snapshot().filter((entry) => entry.kind === "agent" && entry.scope === callingState.scope && entry.status === "cancelled" && entry.checkpointRef !== void 0).at(-1);
12075
+ return priorCancelledRoot?.checkpointRef === void 0 ? {} : { [kBootCheckpoint]: priorCancelledRoot.checkpointRef };
12076
+ })()
11906
12077
  };
11907
12078
  const orchestratorState = { ...callingState };
11908
12079
  if (orchestratorAccount !== void 0) orchestratorState.budgetScope = orchestratorAccount;
@@ -11928,7 +12099,8 @@ function makeOrchestratorWorkflow(goal, opts) {
11928
12099
  limits: { maxTurns: capState?.finalizeTurns ?? 2 },
11929
12100
  ...capState === void 0 ? {} : { estCost: capState.finalizeReserveUsd },
11930
12101
  ...opts?.model === void 0 ? {} : { model: opts.model },
11931
- [kTerminalTool]: { name: FINISH_TOOL_NAME }
12102
+ [kTerminalTool]: { name: FINISH_TOOL_NAME },
12103
+ [kFinalizeReserve]: true
11932
12104
  };
11933
12105
  const finalState = { ...callingState };
11934
12106
  if (orchestratorAccount !== void 0) finalState.budgetScope = orchestratorAccount;
@@ -12109,14 +12281,20 @@ const detection = new AsyncLocalStorage();
12109
12281
  let globalsPatched = false;
12110
12282
  /**
12111
12283
  * Stack line 0 names the Error, line 1 this helper, line 2 the patched
12112
- * global, line 3 the caller whose provenance decides. Library code (a
12113
- * provider SDK, any installed dependency, rulvar's own published dist)
12114
- * lives under node_modules and is exempt: the guard exists for workflow
12115
- * code, which imports from node_modules but does not live there.
12284
+ * global, line 3 the caller whose provenance decides (the layout is
12285
+ * pinned by construction: this helper is only ever called by the two
12286
+ * patched globals). Two origins are exempt: installed dependencies (a
12287
+ * provider SDK, any transitive package, rulvar's own published dist),
12288
+ * which live under node_modules, and Node's own machinery (the undici
12289
+ * transport behind fetch, timers, stream internals), whose frames carry
12290
+ * `node:` specifiers and inherit the run's async context. The guard
12291
+ * exists for workflow code, which imports from both but lives in
12292
+ * neither.
12116
12293
  */
12117
12294
  function libraryCaller() {
12118
12295
  const caller = (/* @__PURE__ */ new Error()).stack?.split("\n")[3];
12119
- return caller !== void 0 && caller.includes("node_modules");
12296
+ if (caller === void 0) return false;
12297
+ return caller.includes("node_modules") || /[(\s]node:/.test(caller);
12120
12298
  }
12121
12299
  /**
12122
12300
  * Patches Date.now and Math.random ONCE per process and never restores:
@@ -12468,7 +12646,7 @@ function createEngine(options) {
12468
12646
  dropped: internals.dropped,
12469
12647
  pending,
12470
12648
  usage: ledger.usage,
12471
- cost: buildCostReport(internals.cost, ledger.usd)
12649
+ cost: costReportFromJournal(replayer.snapshot(), priceUsd)
12472
12650
  };
12473
12651
  if (value !== void 0 && (status === "ok" || status === "exhausted")) outcome.value = value;
12474
12652
  if (wireError !== void 0) outcome.error = wireError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.6.0",
3
+ "version": "1.7.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",