@modelprofile.com/flexharness 3.3.0 → 3.5.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/readme.md CHANGED
@@ -89,6 +89,17 @@ const harness = new FlexHarness<IProjectScope>({
89
89
  maxPendingAdmissionBytes: 128 * 1024 * 1024,
90
90
  maxTerminalEntriesPerSession: 64,
91
91
  },
92
+ subagents: [
93
+ {
94
+ name: 'researcher',
95
+ description: 'Research a focused question and return one final answer.',
96
+ modelHint: 'reasoning-model',
97
+ system: 'Investigate the assigned question. Return a concise evidence-based answer.',
98
+ maxSteps: 8,
99
+ },
100
+ ],
101
+ maxSubagentDepth: 1,
102
+ maxSubagentCallsPerRun: 32,
92
103
  externalErrorProjector: (_error, context) => ({
93
104
  name: 'ModelOperationError',
94
105
  message: `The ${context.source} operation failed.`,
@@ -99,6 +110,42 @@ const harness = new FlexHarness<IProjectScope>({
99
110
 
100
111
  `modelRegistry`, `projectRegistry`, `createProjectTools`, and `closeProjectTools` in this example are application-owned integrations. FlexHarness passes the same run `AbortSignal` to the model resolver and tool provider.
101
112
 
113
+ ## Foreground Subagents
114
+
115
+ `subagents` enables a harness-owned built-in tool named `task`. It is available only when at least one definition exists and the current session depth is below `maxSubagentDepth`. An application `toolProvider` must not return its own `task` tool when subagents are configured. The built-in is foreground-only: the parent tool call does not complete until the child prompt reaches a terminal outcome.
116
+
117
+ The model calls it with this exact input shape:
118
+
119
+ ```typescript
120
+ interface ITaskInput {
121
+ description: string;
122
+ prompt: string;
123
+ subagentType: string;
124
+ taskId?: string;
125
+ }
126
+ ```
127
+
128
+ 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`.
129
+
130
+ 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` and `title`. Child sessions reject direct `prompt()`, `startPrompt()`, `enqueuePrompt()`, and `schedulePrompt()` calls and run only through the foreground `task` 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`.
131
+
132
+ The parent tool part receives `childSessionId` in a cumulative `part.updated` event as soon as the child is acquired. If child model resolution completes, a later cumulative update adds `model`; failures before model resolution leave it absent. The terminal tool part retains every value that became available. A successful task always has model identity and returns bounded JSON:
133
+
134
+ ```typescript
135
+ {
136
+ taskId: 'subagent_...',
137
+ status: 'completed',
138
+ text: 'The child final answer, limited to 64 KiB.',
139
+ model: { provider: '...', model: '...', displayName: '...', variant: '...' },
140
+ }
141
+ ```
142
+
143
+ 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.
144
+
145
+ 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 task call.
146
+
147
+ 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 task execution, before semantic bounds, subagent type/depth validation, permission, or child work. Inputs rejected by the tool schema never start task 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. Task 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.
148
+
102
149
  ## Sessions And Prompts
103
150
 
104
151
  ```typescript
@@ -212,7 +259,7 @@ The reservation save is the admission point. A save failure produces no start ev
212
259
 
213
260
  `listMessagePage()` returns the newest contiguous page in chronological order. `limit` must be an integer from 1 through 50 and defaults to 50. `nextCursor` is opaque, limited to 4096 UTF-8 bytes, bound to the resolved storage namespace and session, and remains stable when newer messages are appended. Mismatched and stale cursors fail validation. `getMessage()` performs an exact lookup. Transfer identifiers are limited to 512 bytes, text and reasoning parts to 96 KiB, complete messages to 480 KiB, and complete page envelopes to 512 KiB. A page may therefore contain fewer messages than requested. Oversized text is truncated and an otherwise oversized parts collection is replaced with an explicit elision marker; metadata that still cannot fit fails validation. Canonical private Agent events are unchanged.
214
261
 
215
- `updateSession()` supports title replacement, explicit title clearing with `null`, and archive state through `archived`. Title-only updates remain available while prompts are queued or running, while permission is pending, and after archival. Requests containing `archived` are rejected while the session has any outstanding prompt or pending permission; a mixed title-and-archive request is rejected atomically without changing the title. Archived sessions expose `archivedAt`. Deletion first hides the session behind a durable tombstone, then cancels queued and active work, rejects pending permissions, emits terminal queue events, waits for runtime cleanup, purges runtime queue status, and removes the complete persisted session across public projections, canonical Agent events and archives, remembered permission grants, and background job state. If cleanup fails, the tombstone remains and the operation is retried by a later `deleteSession()`, namespace load, `retireScope()`, or `dispose()` call.
262
+ `updateSession()` supports title replacement, explicit title clearing with `null`, and archive state through `archived`. Title-only updates remain available while prompts are queued or running, while permission is pending, and after archival. Requests containing `archived` are rejected while the session has any outstanding prompt or pending permission; a mixed title-and-archive request is rejected atomically without changing the title. Archived sessions expose `archivedAt`. Deleting a session cascades through its complete descendant subtree. One durable root-keyed tombstone group hides every newly affected live session, and the delete also joins any already-separate descendant cleanup groups without rewriting their roots. FlexHarness then cancels queued and active subtree work, emits terminal queue events, waits for admitted initialization, and purges runtime queue status while cleaning runtime and persisted domains child-first. The requested root tombstone is removed last. A successful live `deleteSession()` call emits `session.deleted` for each session it newly tombstoned; retries of an existing tombstone and automatic load, retirement, or disposal cleanup emit no deletion events. Direct deletion of a descendant cascades only through that descendant's subtree. Cleanup authority follows the resolved storage namespace, so scope aliases share the same groups. A partial failure retains durable ownership for retry by a later `deleteSession()`, namespace load, `retireScope()`, or `dispose()` call.
216
263
 
217
264
  `abort()` returns `true` only while cancellation is still accepted. Terminal persistence is the run's commit point; once it starts, `abort()` returns `false` and the already-fixed terminal outcome completes while the session remains busy.
218
265
 
@@ -280,7 +327,7 @@ Model resolver, tool provider, AgentSession, tool execution, tool callback, tool
280
327
 
281
328
  Transactional tool calls persist an execution intent before the tool side effect starts. After an interrupted process, `listUncertainToolExecutions()` exposes intents whose outcome cannot be proven. A controller must inspect the external system and call `reconcileToolExecution()` with `executed`, `not-executed`, or `abandoned-unknown` before allowing dependent work to continue. Reconciliation output is normalized using the same tool-output limits.
282
329
 
283
- `agentSessionPolicy` forwards bounded SmartAgent session controls for context building, compaction, event retention, change-listener pressure, lease cleanup, archived transaction tombstones, and context-overflow retries. With a configured `contextCompactor`, `compactSession()` writes a canonical compaction event. `archiveSessionEvents()` moves events covered by that compaction into the configured Agent event archive store and returns public archive metadata.
330
+ `agentSessionPolicy` forwards bounded SmartAgent session controls for context building, compaction, event retention, change-listener pressure, lease cleanup, archived transaction tombstones, and context-overflow retries. A configured `contextCompactor` receives the projected model messages, only the filtered model-visible covered events, SmartAgent's existing `reason` and `abortSignal`, and the exact resolved `scopeId`, `scope`, `storageKey`, and `sessionId` for the invocation causing compaction. The invocation context remains isolated when aliases share one storage key, so integrations can resolve the correct model without global mutable state. If no events are eligible for compaction, `compactSession()` returns without calling the compactor or writing a compaction event; otherwise it writes the canonical event. `archiveSessionEvents()` moves events covered by that compaction into the configured Agent event archive store and returns public archive metadata.
284
331
 
285
332
  `executionContextProvider` can construct a SmartAgent execution context for each session. FlexHarness supplies the resolved scope, storage key, and the session's private job store. The public background APIs expose only execution ID, type, state, exit code, and timestamps; command payloads, stdout, and stderr remain private. The provider's optional `close()` is owned by session deletion, scope retirement, and harness disposal.
286
333
 
@@ -316,7 +363,7 @@ const unsubscribe = harness.subscribe((event) => {
316
363
  unsubscribe();
317
364
  ```
318
365
 
319
- Events are discriminated, sequenced, deeply immutable snapshots. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every `part.started`, `part.delta`, and `part.completed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences. Events contain public IDs and snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, or provider options.
366
+ Events are discriminated, sequenced, deeply immutable snapshots. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every callback-backed streamed text part emits exactly one `part.completed` event before the corresponding `run.finished` event. Every `part.started`, `part.delta`, `part.updated`, and `part.completed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences. `part.updated` is cumulative metadata replacement rather than a text delta. Events contain public IDs and snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, provider options, or raw storage key.
320
367
 
321
368
  ## Stores
322
369
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '3.3.0',
6
+ version: '3.5.0',
7
7
  description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
8
8
  }