@modelprofile.com/flexharness 5.2.0 → 5.3.1

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/readme.md CHANGED
@@ -250,11 +250,15 @@ Public writes use `{ actor: 'application' }`. Tool writes use `{ actor: 'agent',
250
250
 
251
251
  `FLEX_PROJECT_MANAGEMENT_LIMITS` exports the hard UTF-8 and aggregate limits: goal 8 KiB, scratchpad 128 KiB, task content 8 KiB, task ID 512 bytes, title 2048 bytes, 512 tasks, and a 1 MiB serialized snapshot. The aggregate bound leaves room for worst-case JSON escaping of a controller-valid scratchpad. Loaded snapshots reject extra fields, duplicate IDs, invalid status/priority/timestamps, non-JSON data, wrong schema/revision, and every exceeded bound before use.
252
252
 
253
- `IFlexProjectManagementStore` is exact per `(storageKey, sessionId)`: `load`, CAS `save`, CAS `tombstoneSession`, and `purgeNamespace` must not collapse multiple sessions or storage namespaces. `load()` returns `TFlexProjectManagementRecord | undefined` and receives the current session generation as an optional third argument; FlexHarness always supplies it, while two-argument callers and stores remain compatible. Same-generation live saves use normal revision CAS, and a same-generation tombstone permanently rejects later live saves. A higher `sessionGenerationSequence` with a different `sessionGenerationId` may replace only an older tombstone using expected revision `0`; it cannot replace a live record. This resets the PM revision for a recreated core session while stale saves and tombstones from older generations remain fenced. Deleting a recreated session that made no PM writes still replaces the prior-generation tombstone with a revision-1 tombstone for the new generation.
253
+ `IFlexProjectManagementStore` is exact per `(storageKey, sessionId)`: `load`, CAS `save`, CAS `tombstoneSession`, and `purgeNamespace` must not collapse multiple sessions or storage namespaces. `load()` returns `TFlexProjectManagementRecord | undefined` and receives an optional `IFlexProjectManagementSessionContext` as its third argument; `tombstoneSession()` receives the same optional context as its fifth argument. FlexHarness always supplies both, while existing two-argument loads, four-argument tombstones, and shorter store implementations remain compatible.
254
+
255
+ `IFlexProjectManagementSessionContext` contains `sessionGenerationId`, `sessionGenerationSequence`, and optional `subagent`. The atomic `IFlexSubagentProvenance` block contains `parentSessionId`, `parentSessionGenerationId`, `parentSessionGenerationSequence`, `originParentRunId`, `originParentToolCallId`, `agent`, and actual session `depth`. Both the context and its separately cloned nested block are frozen.
256
+
257
+ Same-generation live saves use normal revision CAS, and a same-generation tombstone permanently rejects later live saves. A higher `sessionGenerationSequence` with a different `sessionGenerationId` may replace only an older tombstone using expected revision `0`; it cannot replace a live record. This resets the PM revision for a recreated core session while stale saves and tombstones from older generations remain fenced. Deleting a recreated session that made no PM writes still replaces the prior-generation tombstone with a revision-1 tombstone for the new generation.
254
258
 
255
259
  Every newly created core session exposes and persists a `sessionGenerationId` plus its monotonic `sessionGenerationSequence`. FlexHarness generates a strong random ID when `sessionGenerationId` is omitted. Applications may supply the ID to `createSession()` when they need to persist creation authority before dispatch; a supplied ID must be nonblank, contain no control characters, and fit within `FLEX_SESSION_GENERATION_ID_MAX_BYTES` (128 UTF-8 bytes). FlexHarness still assigns the sequence atomically. A legacy scope session without those fields is assigned a deterministic bounded ID derived from its immutable `storageKey`, `sessionId`, and `createdAt`; FlexHarness persists the repaired scope snapshot before accepting work. Grouped core deletion tombstones retain both fields after live metadata is removed.
256
260
 
257
- Normal Flex session cleanup always waits in-flight local project operations, then loads and CAS-tombstones `stores.projectManagement`, regardless of whether PM tools are enabled in that harness. If a concurrent same-generation save wins first, cleanup reloads and retries within a bounded attempt count; unresolved conflict or store failure retains the core Flex session cleanup tombstone for a later retry. The durable PM tombstone is not physically removed during normal session cleanup.
261
+ Normal Flex session cleanup always waits in-flight local project operations, then loads and CAS-tombstones `stores.projectManagement`, regardless of whether PM tools are enabled in that harness. Child cleanup persists one complete `IFlexSubagentProvenance` block on its core tombstone before live metadata is removed. One cleanup invocation reuses the identical doubly frozen context object across bounded CAS retries; restart or a later cleanup invocation reconstructs a new frozen context from the persisted provenance. If a concurrent same-generation save wins first, cleanup reloads and retries; unresolved conflict or store failure retains the core Flex session cleanup tombstone for a later retry. The durable PM tombstone is not physically removed during normal session cleanup.
258
262
 
259
263
  `purgeNamespace(storageKey)` is the explicit destructive reclamation operation and physically removes every live record and tombstone in that exact PM namespace. Applications may call it only after serializing every scope alias, preventing new admission, awaiting `retireScope()` on every harness owner, and deleting or purging the application-owned core scope namespace. `retireScope()` itself remains non-destructive and never calls `purgeNamespace()`. Purging PM first, purging only one alias, or racing a stale harness can remove the fence that makes session-generation reuse safe.
260
264
 
@@ -275,7 +279,7 @@ interface IDelegateInput {
275
279
  }
276
280
  ```
277
281
 
278
- Before creating or resuming a child, FlexHarness requests permission on the parent run with `kind: 'subagent.start'`, the parent `toolCallId`, and bounded agent/task metadata. The controller answers it through the normal permission APIs. This request has no `rememberKey`, so `always` is invalid; controllers use `once` or `reject`.
282
+ Before creating or resuming a child, FlexHarness requests permission on the parent run with `kind: 'subagent.start'`, the parent `toolCallId`, and separately bounded harness-owned agent/task metadata. Its metadata includes `childSessionId`, the exact ID reserved for this call: FlexHarness derives it deterministically when `taskId` is omitted and copies the supplied candidate when `taskId` is present. A resume also retains that candidate as `taskId`. This block is not truncated by `toolOutputLimits`. The reserved ID binds permission handling before child creation but does not prove that the child exists or is owned: applications must treat the later delegated admission context as authoritative. The controller answers through the normal permission APIs. This request has no `rememberKey`, so `always` is invalid; controllers use `once` or `reject`.
279
283
 
280
284
  Each new invocation creates a durable child `IFlexSession` with immutable `parentSessionId`, origin `parentRunId`, origin `parentToolCallId`, `agent`, and `depth`. New public roots persist `depth: 0`; legacy schema-1 roots may omit it. These fields are harness-owned; public `createSession()` remains limited to `sessionId`, `sessionGenerationId`, and `title`. Child sessions reject direct `prompt()`, `startPrompt()`, `enqueuePrompt()`, and `schedulePrompt()` calls and run only through the foreground `delegate` tool. The model and tool resolver contexts receive optional immutable `parentSessionId` and `agent` values so integrations can apply agent-specific model and tool policy. Child prompts use the definition's `modelHint`, `system`, and `maxSteps`.
281
285
 
@@ -290,11 +294,49 @@ The parent tool part receives `childSessionId` in a cumulative `part.updated` ev
290
294
  }
291
295
  ```
292
296
 
293
- Omitting `taskId` creates a deterministic child for the parent session, run, and tool call. Repeating that same invocation does not create another child. If the deterministic child already has messages, FlexHarness reports an uncertain prior execution and never silently reruns it. This preserves SmartAgent's durable parent tool intent as crash authority; controllers use `listUncertainToolExecutions()` and `reconcileToolExecution()` for uncertain parent calls.
297
+ Omitting `taskId` creates a deterministic child for the parent session, run, and tool call. The model-visible tool description and `taskId` schema state this creation rule directly. Repeating that same invocation does not create another child. If the deterministic child already has messages, FlexHarness reports an uncertain prior execution and never silently reruns it. This preserves SmartAgent's durable parent tool intent as crash authority; controllers use `listUncertainToolExecutions()` and `reconcileToolExecution()` for uncertain parent calls.
298
+
299
+ Supplying `taskId` deliberately resumes an idle, live child from a later run of the same immutable parent session and the same configured agent. The caller must use the exact ID returned by an earlier completed delegate call; an unknown ID fails with safe corrective guidance and never creates a child under the supplied label. Resume starts a new child prompt while retaining the child's original parent run and tool-call origin. A child owned by another parent or agent, a deleted child, an active child, a same-run resume, or a second acquisition of the same child within one later parent run is rejected. Parent cancellation propagates only to the exact child run started by that delegate call.
300
+
301
+ `delegatedRunAdmissionProvider` optionally adds an application-owned admission lease around each internally delegated child run. The public contracts are `IFlexDelegatedRunAdmissionProvider<TScope>`, `IFlexDelegatedRunAdmissionContext<TScope>`, and `IFlexDelegatedRunAdmissionLease`. The provider is never called for root prompts or direct public prompt APIs. It receives a frozen context containing the resolved `scopeId`, exact captured `scope`, and `storageKey`; the exact child `sessionId`, session generation, queue, and run; the exact current parent session generation, queue, run, and delegate `toolCallId`; immutable `originParentRunId` and `originParentToolCallId`; the child agent and depth; and the child run `AbortSignal`. For `taskId` resume, the current parent queue/run/tool-call fields identify this delegate invocation, while the origin fields remain fixed to the invocation that created the durable child.
302
+
303
+ ```typescript
304
+ delegatedRunAdmissionProvider: {
305
+ async acquireDelegatedRunAdmission(context) {
306
+ const admission = await controller.acquireDelegatedRun({
307
+ child: {
308
+ sessionId: context.sessionId,
309
+ sessionGenerationId: context.sessionGenerationId,
310
+ sessionGenerationSequence: context.sessionGenerationSequence,
311
+ queueId: context.queueId,
312
+ runId: context.runId,
313
+ },
314
+ parent: {
315
+ sessionId: context.parentSessionId,
316
+ sessionGenerationId: context.parentSessionGenerationId,
317
+ sessionGenerationSequence: context.parentSessionGenerationSequence,
318
+ queueId: context.parentQueueId,
319
+ runId: context.parentRunId,
320
+ toolCallId: context.parentToolCallId,
321
+ originRunId: context.originParentRunId,
322
+ originToolCallId: context.originParentToolCallId,
323
+ },
324
+ signal: context.signal,
325
+ });
326
+ return {
327
+ close: () => admission.close(),
328
+ };
329
+ },
330
+ },
331
+ ```
332
+
333
+ Acquisition completes before generation-side branch reversion, context compaction, model resolution, application or resource tool-provider callbacks, and model execution. Child session store and runtime initialization may already have occurred before acquisition. Providers must honor the supplied `AbortSignal`; an abort can settle the active child and parent without waiting for an acquisition that ignores cancellation, while FlexHarness retains ownership and closes any lease returned later.
334
+
335
+ For a normally acquired lease, FlexHarness gives `close()` an awaited attempt after SmartAgent generation and before canonical accepted, rejected, or interrupted finalization and the terminal `prompt.finished` event. If an abort detaches an acquisition that ignores its signal, the run may settle before acquisition returns; FlexHarness retains that owner and closes any late lease. `close()` must be idempotent and safe to retry after rejection. A close failure prevents successful child acceptance and remains owned by the exact child session generation for retry by later exact-session deletion, scope retirement, or disposal. Retirement and disposal truthfully wait for late acquisition and lease cleanup.
294
336
 
295
- Supplying `taskId` deliberately resumes an idle, live child from a later run of the same immutable parent session and the same configured agent. It starts a new child prompt while retaining the child's original parent run and tool-call origin. A child owned by another parent or agent, a deleted child, an active child, a same-run resume, or a second acquisition of the same child within one later parent run is rejected. Parent cancellation propagates only to the exact child run started by that delegate call.
337
+ Limits are validated and frozen at construction: at most 32 unique definitions; names are non-empty and at most 128 UTF-8 bytes; descriptions 2048 bytes; optional model hints 512 bytes; optional system prompts 64 KiB; and optional `maxSteps` a positive safe integer. `maxSubagentDepth` defaults to 1 and must be a positive safe integer at most 8. `maxSubagentCallsPerRun` defaults to 32 and must be a positive safe integer at most 128. A call slot is consumed synchronously at the start of every schema-valid delegate execution, before semantic bounds, subagent type/depth validation, permission, or child work. Inputs rejected by the tool schema never start delegate execution and do not consume a slot. After successful semantic validation, the child ID candidate is reserved for the rest of the parent run, including after permission rejection or later failure. Permission rejection creates no child session. Omitting `taskId` reserves a deterministic new child ID; supplying `taskId` reserves that unverified candidate and attempts resume after permission only if it identifies a resumable child. Delegate descriptions are non-empty and at most 256 UTF-8 bytes, prompts non-empty and at most 64 KiB, subagent types at most 128 bytes, and task IDs at most 512 bytes.
296
338
 
297
- Limits are validated and frozen at construction: at most 32 unique definitions; names are non-empty and at most 128 UTF-8 bytes; descriptions 2048 bytes; optional model hints 512 bytes; optional system prompts 64 KiB; and optional `maxSteps` a positive safe integer. `maxSubagentDepth` defaults to 1 and must be a positive safe integer at most 8. `maxSubagentCallsPerRun` defaults to 32 and must be a positive safe integer at most 128. A call slot is consumed synchronously at the start of every schema-valid delegate execution, before semantic bounds, subagent type/depth validation, permission, or child work. Inputs rejected by the tool schema never start delegate execution and do not consume a slot. After successful semantic validation, the child ID is reserved for the rest of the parent run, including after permission rejection or later failure. Permission rejection creates no child session. Omitting `taskId` reserves a deterministic new child ID; supplying `taskId` reserves and resumes that existing child after permission. Delegate descriptions are non-empty and at most 256 UTF-8 bytes, prompts non-empty and at most 64 KiB, subagent types at most 128 bytes, and task IDs at most 512 bytes.
339
+ Scope snapshots remain schema 1 and legacy child tombstones without `subagent` provenance continue to load. New child tombstones write the atomic provenance block and reject partial, parent-generation-mismatched, depth-mismatched, or duplicate-origin records. FlexHarness versions before this provenance addition reject that new optional key under their strict reader, so downgrading or mixing old readers with newly written scope snapshots is unsupported.
298
340
 
299
341
  ## Sessions And Prompts
300
342
 
@@ -661,9 +703,9 @@ FlexHarness wraps every provided tool `execute` method before SmartAgent receive
661
703
 
662
704
  Streaming callbacks use run-local synchronous state rather than one persistence promise per source delta. Text and reasoning accumulate only in the run-local terminal projection while each source delta remains an immediate exact public event. Every distinct async-iterable tool output appears immediately as a bounded cumulative `part.updated` snapshot while the tool remains `running`, including the final yielded value before completion. Only the authoritative `part.completed` output enters the terminal projection, and failed or interrupted tools discard their transient output. `callbackLimits` bounds callback events, accumulated output bytes, and part count; overflow aborts internally with `FlexHarnessCallbackOverflowError` and the turn is recorded as failed. Reservation and terminal finalization are the normal persistence checkpoints, with permission state changes as explicit additional checkpoints.
663
705
 
664
- Model resolver, tool provider, AgentSession, tool execution, tool callback, tool cleanup, and run-persistence failures cross an untrusted error boundary. By default they become a fixed immutable `FlexHarnessExternalError` before completion rejection, persistence, events, or detached-cleanup reporting. Raw external messages and aggregate members are not retained. A failed `onToolCallFinish` callback stores and accounts for only the bounded projected message; it does not otherwise reject completion, although exceeding the configured callback limits still fails the run. Scope resolution and the initial store load happen before a run exists and remain outside this boundary.
706
+ Model resolver, tool provider, delegated run admission provider, AgentSession, tool execution, tool callback, tool cleanup, and run-persistence failures cross an untrusted error boundary. By default they become a fixed immutable `FlexHarnessExternalError` before completion rejection, persistence, events, or detached-cleanup reporting. Raw external messages and aggregate members are not retained. A failed `onToolCallFinish` callback stores and accounts for only the bounded projected message; it does not otherwise reject completion, although exceeding the configured callback limits still fails the run. Scope resolution and the initial store load happen before a run exists and remain outside this boundary.
665
707
 
666
- `externalErrorProjector` receives one of `modelResolver`, `toolProvider`, `agentSession`, `toolExecution`, `toolCallback`, `toolCleanup`, `persistence`, `slashCommand`, or `turnReversion` as its source. It may synchronously return an application-approved plain data object `{ name, message, code? }`, limited to a 128-byte name, 2048-byte message, and optional 128-byte code. Accessors, extra keys, throwing projectors, and malformed or oversized results fall back to the fixed error. Even exported FlexHarness error subclasses thrown by external integrations are reprojected. Internally created cancellation, callback-overflow, and permission errors retain their typed behavior.
708
+ `externalErrorProjector` receives one of `modelResolver`, `toolProvider`, `delegatedRunAdmissionProvider`, `agentSession`, `toolExecution`, `toolCallback`, `toolCleanup`, `persistence`, `slashCommand`, or `turnReversion` as its source. It may synchronously return an application-approved plain data object `{ name, message, code? }`, limited to a 128-byte name, 2048-byte message, and optional 128-byte code. Accessors, extra keys, throwing projectors, and malformed or oversized results fall back to the fixed error. Even exported FlexHarness error subclasses thrown by external integrations are reprojected. Internally created cancellation, callback-overflow, and permission errors retain their typed behavior.
667
709
 
668
710
  `normalizeJsonValue()` is also exported for integrations that need the same conversion independently.
669
711
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '5.2.0',
6
+ version: '5.3.1',
7
7
  description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
8
8
  }