@modelprofile.com/flexharness 3.4.0 → 3.6.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/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +24 -0
- package/dist_ts/classes.flexharness.js +920 -97
- package/dist_ts/interfaces.d.ts +43 -1
- package/dist_ts/plugins.d.ts +2 -2
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.json.js +176 -8
- package/package.json +1 -1
- package/readme.hints.md +13 -0
- package/readme.md +84 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +1203 -106
- package/ts/interfaces.ts +50 -1
- package/ts/plugins.ts +4 -0
- package/ts/utils.json.ts +202 -8
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,76 @@ 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
|
+
## Resource Tool Providers
|
|
114
|
+
|
|
115
|
+
`resourceToolProviderResolver` composes zero or more resource-owned providers with the existing application `toolProvider`. The resolver runs fresh for every prompt and returns the current resource attachment descriptors:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
resourceToolProviderResolver: {
|
|
119
|
+
async resolveResourceToolProviders({ scope, sessionId, runId, signal }) {
|
|
120
|
+
const attachments = await resourceRegistry.listAttached({
|
|
121
|
+
scope,
|
|
122
|
+
sessionId,
|
|
123
|
+
runId,
|
|
124
|
+
signal,
|
|
125
|
+
});
|
|
126
|
+
return attachments.map((attachment) => ({
|
|
127
|
+
resourceId: attachment.resourceId,
|
|
128
|
+
attachmentRevision: attachment.attachmentRevision,
|
|
129
|
+
provider: createResourceToolProvider(attachment),
|
|
130
|
+
}));
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each descriptor uses the existing `IFlexToolProvider<TScope>` contract. Its provider receives the normal run context and must return a fresh run-scoped handle. The original `toolProvider` remains optional and its tool names remain unchanged. Resource tool names are deterministic and bounded:
|
|
136
|
+
|
|
137
|
+
1. `resourceIdentity` is the lowercase hexadecimal SHA-256 of `JSON.stringify([resourceId, attachmentRevision])`.
|
|
138
|
+
2. The namespace is `resource_` plus the first 16 digest characters.
|
|
139
|
+
3. The exposed name is `<namespace>__<stem>__<toolDigest>`. `stem` replaces characters outside `[A-Za-z0-9_-]` with `_`, keeps the first 16 characters, and falls back to `tool`; `toolDigest` is the first 12 lowercase hexadecimal characters of SHA-256 over the original tool name.
|
|
140
|
+
|
|
141
|
+
The resolver accepts at most 128 descriptors per run. `resourceId` must be non-empty and at most 512 UTF-8 bytes, `attachmentRevision` must be a non-negative safe integer, and each original resource tool name must be non-empty and at most 512 UTF-8 bytes. FlexHarness rejects duplicate `resourceId` values even across revisions, duplicate derived namespaces, and duplicate final exposed tool names before model execution. Descriptor identity and namespace validation completes before any application or resource provider is acquired.
|
|
142
|
+
|
|
143
|
+
Resource permission requests are scoped with the complete 64-character `resourceIdentity`, not the shortened tool namespace. FlexHarness rewrites `kind` to `resource.<resourceIdentity>.<providerKind>` and an optional `rememberKey` to `resource:<resourceIdentity>:<providerRememberKey>`. Harness-owned metadata contains `resourceId`, `attachmentRevision`, `resourceIdentity`, and `toolNamespace`; provider metadata is nested under `providerMetadata`, so it cannot override attachment identity.
|
|
144
|
+
|
|
145
|
+
FlexHarness owns every acquired handle. Normal close and partial-failure cleanup run in reverse acquisition order, attempt every handle, aggregate multiple failures, and retain failed cleanup for retirement or disposal retry. Cancellation uses the same path. If model resolution fails while a resource provider is still settling, a late returned handle remains tracked and disposal waits for its closure.
|
|
146
|
+
|
|
147
|
+
## Foreground Subagents
|
|
148
|
+
|
|
149
|
+
`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.
|
|
150
|
+
|
|
151
|
+
The model calls it with this exact input shape:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
interface ITaskInput {
|
|
155
|
+
description: string;
|
|
156
|
+
prompt: string;
|
|
157
|
+
subagentType: string;
|
|
158
|
+
taskId?: string;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
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`.
|
|
163
|
+
|
|
164
|
+
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`.
|
|
165
|
+
|
|
166
|
+
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:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
{
|
|
170
|
+
taskId: 'subagent_...',
|
|
171
|
+
status: 'completed',
|
|
172
|
+
text: 'The child final answer, limited to 64 KiB.',
|
|
173
|
+
model: { provider: '...', model: '...', displayName: '...', variant: '...' },
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
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.
|
|
178
|
+
|
|
179
|
+
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.
|
|
180
|
+
|
|
181
|
+
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.
|
|
182
|
+
|
|
102
183
|
## Sessions And Prompts
|
|
103
184
|
|
|
104
185
|
```typescript
|
|
@@ -212,7 +293,7 @@ The reservation save is the admission point. A save failure produces no start ev
|
|
|
212
293
|
|
|
213
294
|
`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
295
|
|
|
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`.
|
|
296
|
+
`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
297
|
|
|
217
298
|
`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
299
|
|
|
@@ -316,7 +397,7 @@ const unsubscribe = harness.subscribe((event) => {
|
|
|
316
397
|
unsubscribe();
|
|
317
398
|
```
|
|
318
399
|
|
|
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 callback-backed streamed text part emits exactly one `part.completed` event before the corresponding `run.finished` event. 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
|
|
400
|
+
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
401
|
|
|
321
402
|
## Stores
|
|
322
403
|
|
|
@@ -375,7 +456,7 @@ await migrateLegacyFlexHarnessSnapshot(storageKey, legacySnapshot, stores);
|
|
|
375
456
|
|
|
376
457
|
## Shutdown
|
|
377
458
|
|
|
378
|
-
Model and tool resolution share the run signal. Synchronous throws are observed as resolver failures; the first failure aborts that signal and finalizes immediately without waiting for an unresponsive sibling. A detached tool provider that resolves later is observed and
|
|
459
|
+
Model and tool resolution share the run signal. Synchronous throws are observed as resolver failures; the first failure aborts that signal and finalizes immediately without waiting for an unresponsive sibling. A detached application or resource tool provider that resolves later is observed and every acquired handle is closed; disposal waits for that settlement and reports a late close failure.
|
|
379
460
|
|
|
380
461
|
Tool-handle close settles before a turn can be successful. After model generation resolves, FlexHarness stages its terminal projection before canonical acceptance; earlier execution failures are finalized as interrupted and then published from that durable outcome. Cleanup failure prevents canonical acceptance. If canonical finalization or public promotion fails, FlexHarness fences the namespace; the next load repairs public state from the durable canonical outcome and any hidden terminal stage.
|
|
381
462
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/flexharness',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.6.0',
|
|
7
7
|
description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
|
|
8
8
|
}
|