@modelprofile.com/flexharness 2.1.0 → 3.0.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/changelog.md +66 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +86 -30
- package/dist_ts/classes.flexharness.js +2593 -1131
- package/dist_ts/classes.stores.d.ts +19 -20
- package/dist_ts/classes.stores.js +968 -75
- package/dist_ts/errors.d.ts +5 -0
- package/dist_ts/errors.js +11 -1
- package/dist_ts/interfaces.d.ts +130 -23
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.json.d.ts +4 -2
- package/dist_ts/utils.json.js +283 -140
- package/dist_ts/utils.prompt.d.ts +1 -2
- package/dist_ts/utils.prompt.js +3 -26
- package/dist_ts_migration/index.d.ts +1 -0
- package/dist_ts_migration/index.js +2 -0
- package/dist_ts_migration/v3_legacyflexharness.d.ts +17 -0
- package/dist_ts_migration/v3_legacyflexharness.js +426 -0
- package/package.json +17 -3
- package/readme.hints.md +13 -11
- package/readme.md +94 -27
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +2966 -1421
- package/ts/classes.stores.ts +1226 -97
- package/ts/errors.ts +20 -0
- package/ts/interfaces.ts +168 -22
- package/ts/plugins.ts +46 -3
- package/ts/utils.json.ts +341 -154
- package/ts/utils.prompt.ts +6 -27
- package/ts_migration/index.ts +1 -0
- package/ts_migration/v3_legacyflexharness.ts +620 -0
package/readme.hints.md
CHANGED
|
@@ -4,25 +4,27 @@ Implementation findings for flexharness.
|
|
|
4
4
|
|
|
5
5
|
## SmartAgent boundary
|
|
6
6
|
|
|
7
|
-
- SmartAgent is the
|
|
7
|
+
- SmartAgent is the canonical private conversation and execution runtime. FlexHarness creates one transactional `AgentSession` per public session and derives its public model, prompt, provider-option, tool-set, and result aliases from SmartAgent exports.
|
|
8
8
|
- Public prompts remain JSON-safe. URL strings are converted to `URL` instances only at the private SmartAgent invocation boundary.
|
|
9
|
-
- Prompt attachment payloads exist only in
|
|
9
|
+
- Prompt attachment payloads exist only in canonical private Agent events. Public messages, prompt results, projection snapshots, and events expose only `attachmentType`, source kind, optional media/name, and decoded size when determinable.
|
|
10
10
|
- Resolver calls start in promise continuations so synchronous throws are observed. The first failure aborts the shared internal signal without awaiting an ignoring sibling; detached tool-provider settlement is observed and late handles are closed.
|
|
11
11
|
|
|
12
12
|
## Persistence boundary
|
|
13
13
|
|
|
14
14
|
- Snapshot schema version 1 uses optimistic revisions. Harness mutations are serialized per resolved storage key, while the JSON store adds a process-wide per-file queue shared by all instances.
|
|
15
|
-
- Every queued mutation snapshots
|
|
16
|
-
- `
|
|
17
|
-
-
|
|
18
|
-
- Session
|
|
19
|
-
- Tool close settles before
|
|
20
|
-
- Disposal waits finalizers
|
|
21
|
-
-
|
|
15
|
+
- Every queued mutation snapshots its revision and domain state first. A thrown save is reconciled against the store before memory is restored: a proven prior snapshot rolls back, a proven new snapshot remains current, and an unknown or explicitly uncertain commit fences the namespace. Code that crosses the save `await` must re-fetch session records because a proven rollback intentionally restores them.
|
|
16
|
+
- `JsonFileFlexHarnessStores` is intentionally not cross-process safe. It provides atomic rename and in-process CAS, not an operating-system lock.
|
|
17
|
+
- Loaded state is repaired from canonical generation outcomes. Accepted hidden terminal stages are promoted, while interrupted or incomplete public messages and tool parts become cancelled so a restarted process never presents them as still running.
|
|
18
|
+
- Session updates reject while runtime work is active. Deletion tombstones the session, cancels active work, and retains failed runtime cleanup for a later deletion, retirement, or disposal retry before removing every persisted domain.
|
|
19
|
+
- Tool close settles before canonical acceptance. A completed hidden terminal projection is staged first, the Agent generation is finalized second, and only then is the public projection promoted. Earlier execution failures finalize as interrupted and publish from that durable outcome.
|
|
20
|
+
- Disposal waits admitted session initialization, finalizers, every loaded-state save tail, tombstone cleanup, provider release, and detached tool cleanup. Listeners always clear; state caches clear only after cleanup succeeds and otherwise remain owned for retry.
|
|
21
|
+
- Canonical finalization is the cancellation linearization point. Abort returns false once a run starts committing, while the active-run entry continues to block new prompts until finalization and public promotion settle.
|
|
22
22
|
- Permission callbacks wait for the current save tail and then revalidate their run before reading remembered grants, so neither uncommitted grants nor captured callbacks can authorize later work.
|
|
23
|
-
- Detached tool-provider settlement remains tracked after prompt finalization.
|
|
23
|
+
- Detached tool-provider settlement remains tracked after prompt finalization. Failed late handle closure stays owned by its storage state and is retried by a later retirement or disposal call.
|
|
24
24
|
- Scope retirement fences older resolver calls and late state admissions, drains the complete resolved storage namespace, and evicts only the exact cached state promise. It never deletes the durable snapshot. Applications must close admission across every resolver alias before retirement because aliases are unknowable before resolution.
|
|
25
|
-
- Detached tool-provider cleanup and retained cleanup
|
|
25
|
+
- Detached tool-provider cleanup and retained session cleanup are owned by the exact loaded storage state. Failed drains retain that ownership and the cached state for retry. Scope retirement cannot await or consume another namespace's cleanup, while full disposal settles all storage drains and orphaned initialization cleanup before aggregating failures.
|
|
26
|
+
- JSON provider release hooks evict non-destructive session wrapper caches after AgentSession and execution-context ownership ends. Failed partial-initialization releases remain storage-scoped orphan ownership and are retried by retirement or disposal.
|
|
27
|
+
- `JsonFileFlexHarnessStores.dispose()` is the explicit final drain for file handles whose close failed on the last store operation; failed disposal retains the handle for another call.
|
|
26
28
|
|
|
27
29
|
## Tool output boundary
|
|
28
30
|
|
package/readme.md
CHANGED
|
@@ -16,14 +16,14 @@ Node.js 24 or newer is required.
|
|
|
16
16
|
|
|
17
17
|
## Overview
|
|
18
18
|
|
|
19
|
-
FlexHarness owns
|
|
19
|
+
FlexHarness owns scope isolation, public session and message projections, permission decisions, event delivery, cancellation, and persistence coordination. Each session is backed by a SmartAgent `AgentSession`, which owns the canonical private conversation and runtime event history. Model selection, tool execution, and optional execution-context creation remain application-defined extension points. The package depends on SmartAgent but does not expose AI SDK or SmartAI imports as part of its API.
|
|
20
20
|
|
|
21
21
|
## Core Setup
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
24
|
import {
|
|
25
25
|
FlexHarness,
|
|
26
|
-
|
|
26
|
+
JsonFileFlexHarnessStores,
|
|
27
27
|
type IFlexResolvedModel,
|
|
28
28
|
type TFlexAgentToolSet,
|
|
29
29
|
} from '@modelprofile.com/flexharness';
|
|
@@ -70,7 +70,7 @@ const harness = new FlexHarness<IProjectScope>({
|
|
|
70
70
|
};
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
|
-
|
|
73
|
+
stores: new JsonFileFlexHarnessStores({
|
|
74
74
|
directory: '/var/lib/my-app/model-sessions',
|
|
75
75
|
}),
|
|
76
76
|
toolOutputLimits: {
|
|
@@ -140,7 +140,7 @@ Attachment payloads are never copied into public audit messages or events. Publi
|
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
When the turn succeeds, the original string remains only in private
|
|
143
|
+
When the turn succeeds, the original string remains only in canonical private Agent events so a later model turn can receive the attachment again. Failed, cancelled, resolver-failed, cleanup-failed, and persistence-failed turns do not add it to future context.
|
|
144
144
|
|
|
145
145
|
The main session methods are:
|
|
146
146
|
|
|
@@ -158,44 +158,65 @@ await harness.prompt(scopeId, sessionId, prompt, options);
|
|
|
158
158
|
const admission = await harness.startPrompt(scopeId, sessionId, prompt, options);
|
|
159
159
|
console.log(admission.runId);
|
|
160
160
|
await admission.completion;
|
|
161
|
+
const scheduled = await harness.schedulePrompt(
|
|
162
|
+
scopeId,
|
|
163
|
+
sessionId,
|
|
164
|
+
'refresh-index',
|
|
165
|
+
prompt,
|
|
166
|
+
{ debounceMs: 250 },
|
|
167
|
+
);
|
|
168
|
+
await harness.cancelScheduledPrompt(scopeId, sessionId, scheduled.scheduleKey);
|
|
161
169
|
await harness.abort(scopeId, sessionId);
|
|
162
170
|
await harness.listPendingPermissions(scopeId, sessionId);
|
|
163
171
|
await harness.respondToPermission(scopeId, sessionId, permissionId, 'once');
|
|
172
|
+
await harness.pushRuntimeEvent(scopeId, sessionId, { type: 'workspace.changed', path: 'src/' });
|
|
173
|
+
await harness.listUncertainToolExecutions(scopeId, sessionId);
|
|
174
|
+
await harness.reconcileToolExecution(scopeId, sessionId, intentId, {
|
|
175
|
+
resolution: 'executed',
|
|
176
|
+
output: { committed: true },
|
|
177
|
+
});
|
|
178
|
+
await harness.compactSession(scopeId, sessionId);
|
|
179
|
+
await harness.archiveSessionEvents(scopeId, sessionId, compactionEventId);
|
|
180
|
+
await harness.listBackgroundExecutions(scopeId, sessionId);
|
|
181
|
+
await harness.getBackgroundExecution(scopeId, sessionId, executionId);
|
|
182
|
+
await harness.abortBackgroundExecution(scopeId, sessionId, executionId);
|
|
164
183
|
await harness.retireScope(scopeId);
|
|
165
184
|
await harness.dispose();
|
|
166
185
|
```
|
|
167
186
|
|
|
168
|
-
Only one run may be active in a session. Different sessions can run concurrently. `startPrompt()` resolves with `{ runId, completion }` only after the run ID and initial audit messages have been reserved
|
|
187
|
+
Only one run may be active in a session. Different sessions can run concurrently. `startPrompt()` resolves with `{ runId, completion }` only after the canonical generation claim, run ID, and initial public audit messages have been durably reserved and the corresponding start events have been emitted. `prompt()` preserves the simpler behavior by awaiting that completion internally.
|
|
188
|
+
|
|
189
|
+
`schedulePrompt()` performs the same durable admission immediately, exposes session status `scheduled`, and starts model preparation after its bounded `debounceMs` delay. `cancelScheduledPrompt()` returns `true` only while the matching schedule key can still be cancelled; its completion rejects with `FlexHarnessAbortError` and the reserved audit messages become cancelled.
|
|
169
190
|
|
|
170
|
-
The reservation save is the admission point. A save failure produces no start events or active audit. If disposal begins while that save is in flight and the save commits, admission still resolves and its completion settles as cancelled; disposal waits for terminal finalization.
|
|
191
|
+
The reservation save is the admission point. A save failure produces no start events or active audit. If disposal begins while that save is in flight and the save commits, admission still resolves and its completion settles as cancelled; disposal waits for terminal finalization.
|
|
171
192
|
|
|
172
|
-
`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.
|
|
193
|
+
`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.
|
|
173
194
|
|
|
174
|
-
`updateSession()` supports title replacement, explicit title clearing with `null`, and archive state through `archived`. Archived sessions expose `archivedAt`.
|
|
195
|
+
`updateSession()` supports title replacement, explicit title clearing with `null`, and archive state through `archived`. Archived sessions expose `archivedAt`. Updates are rejected while the session has an active run or pending permission. Deletion first hides the session behind a durable tombstone, then cancels active work, rejects pending permissions, waits for runtime cleanup, 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.
|
|
175
196
|
|
|
176
197
|
`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.
|
|
177
198
|
|
|
178
|
-
`retireScope()` stops runtime ownership for the complete resolved storage namespace without deleting its durable snapshot. It does not load a namespace that has no cached or in-flight state. For loaded state, it preserves and waits for persistence that has already started, while later queued reads, writes, and run admissions reject with `FlexHarnessAbortError`. It aborts cancellable runs, rejects pending permissions, waits for committing runs, terminal persistence, tool-handle closure, and detached tool-provider cleanup, then clears and evicts the cached state. Calls through storage-key aliases share the same retirement drain. A later call can load the durable namespace again if the application still resolves it.
|
|
199
|
+
`retireScope()` stops runtime ownership for the complete resolved storage namespace without deleting its durable snapshot. It does not load a namespace that has no cached or in-flight state. For loaded state, it preserves and waits for persistence that has already started, while later queued reads, writes, and run admissions reject with `FlexHarnessAbortError`. It aborts cancellable runs, rejects pending permissions, waits for committing runs, terminal persistence, tool-handle closure, and detached tool-provider cleanup, then clears and evicts the cached state. Failed cleanup ownership remains cached so a later `retireScope()` or `dispose()` call can retry it. Calls through storage-key aliases share the same retirement drain. A later call can load the durable namespace again after successful retirement if the application still resolves it.
|
|
179
200
|
|
|
180
|
-
Normal retirement-induced cancellation does not make `retireScope()` reject. Unexpected failures observed through run finalization or scoped
|
|
201
|
+
Normal retirement-induced cancellation does not make `retireScope()` reject. Unexpected failures observed through run finalization or scoped cleanup are surfaced without dropping the resources that still require cleanup. One such failure is thrown directly; multiple failures are reported through `FlexHarnessRunError`. Calling retirement or disposal again retries retained cleanup ownership.
|
|
181
202
|
|
|
182
203
|
Applications removing a scope must stop and serialize new admission across every alias before calling `retireScope()`, await retirement, and only then remove or purge application-owned durable records. FlexHarness cannot discover aliases before the application resolver returns. Integrations must not use retirement itself as durable deletion.
|
|
183
204
|
|
|
184
205
|
## History And Audit Behavior
|
|
185
206
|
|
|
186
|
-
|
|
207
|
+
The model context is built by the session's canonical SmartAgent event history as:
|
|
187
208
|
|
|
188
|
-
1. Previous
|
|
209
|
+
1. Previous canonically accepted generations.
|
|
189
210
|
2. The normalized current user message.
|
|
190
211
|
3. SmartAgent's result messages.
|
|
191
212
|
|
|
192
|
-
A failed or cancelled prompt remains visible through `getMessages()`, with `failed` or `cancelled` status, but is not included in future model context.
|
|
213
|
+
A failed or cancelled prompt remains visible through `getMessages()`, with `failed` or `cancelled` status, but is not included in future model context. Canonical Agent events remain private and are not exposed by the session or message APIs.
|
|
193
214
|
|
|
194
215
|
Resolved model identity contains provider and model IDs plus optional display name and effective `variant`. The identity, including its variant, is attached to a failed assistant message when resolution completed before a later failure, matching the provider/model behavior. Prompt results contain it only on success.
|
|
195
216
|
|
|
196
|
-
Public audit history is safe to send to controllers: attachment parts contain source and size metadata, never inline base64, data URLs, or remote URL payloads.
|
|
217
|
+
Public audit history is safe to send to controllers: attachment parts contain source and size metadata, never inline base64, data URLs, or remote URL payloads. Canonical private Agent events retain those values solely for subsequent model turns.
|
|
197
218
|
|
|
198
|
-
Sessions expose `idle`, `running`, `waiting_permission`, `failed`, and `cancelled` status. Persisted
|
|
219
|
+
Sessions expose `idle`, `scheduled`, `running`, `waiting_permission`, `failed`, and `cancelled` status. Persisted non-terminal activity is repaired from canonical Agent generation outcomes after process restart; incomplete messages and parts normalize to `cancelled` unless an accepted hidden terminal stage can be promoted.
|
|
199
220
|
|
|
200
221
|
## Permissions
|
|
201
222
|
|
|
@@ -227,12 +248,22 @@ FlexHarness wraps every provided tool `execute` method before SmartAgent receive
|
|
|
227
248
|
|
|
228
249
|
Streaming callbacks use run-local synchronous state rather than one persistence promise per delta. Adjacent text and reasoning deltas coalesce. `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.
|
|
229
250
|
|
|
230
|
-
Model resolver, tool provider,
|
|
251
|
+
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.
|
|
231
252
|
|
|
232
|
-
`externalErrorProjector` receives one of `modelResolver`, `toolProvider`, `
|
|
253
|
+
`externalErrorProjector` receives one of `modelResolver`, `toolProvider`, `agentSession`, `toolExecution`, `toolCallback`, `toolCleanup`, or `persistence` 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.
|
|
233
254
|
|
|
234
255
|
`normalizeJsonValue()` is also exported for integrations that need the same conversion independently.
|
|
235
256
|
|
|
257
|
+
## Agent Runtime Operations
|
|
258
|
+
|
|
259
|
+
`pushRuntimeEvent()` appends a validated JSON event through the canonical AgentSession event store. It is intended for controller-owned context such as workspace changes or external notifications; invalid or non-JSON values fail before persistence.
|
|
260
|
+
|
|
261
|
+
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.
|
|
262
|
+
|
|
263
|
+
`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.
|
|
264
|
+
|
|
265
|
+
`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.
|
|
266
|
+
|
|
236
267
|
## Events
|
|
237
268
|
|
|
238
269
|
```typescript
|
|
@@ -263,34 +294,70 @@ Events are discriminated, sequenced, deeply immutable snapshots. Listener except
|
|
|
263
294
|
|
|
264
295
|
## Stores
|
|
265
296
|
|
|
266
|
-
`
|
|
297
|
+
FlexHarness `3.x` separates persistence by trust and lifecycle domain through `IFlexHarnessStores`:
|
|
298
|
+
|
|
299
|
+
- `scopes`: session metadata and deletion tombstones for a resolved storage namespace.
|
|
300
|
+
- `projections`: public audit messages and hidden terminal stages per session.
|
|
301
|
+
- `permissions`: remembered permission keys per session.
|
|
302
|
+
- `agentEvents`: canonical private SmartAgent events and archives per session.
|
|
303
|
+
- `jobs`: private background execution state per session.
|
|
267
304
|
|
|
268
|
-
`
|
|
305
|
+
`InMemoryFlexHarnessStores` implements all five domains with revision-based compare-and-swap behavior for tests and ephemeral processes. It is the default when `stores` is omitted.
|
|
269
306
|
|
|
270
|
-
-
|
|
307
|
+
Custom Agent event and job providers may implement `releaseSession(storageKey, sessionId)` to release session-bound wrappers, handles, or caches without deleting durable data. FlexHarness calls these hooks only after the corresponding AgentSession or execution context has released runtime ownership. A failed release remains owned for a later retirement or disposal retry. `deleteSession()` remains the separate destructive operation for durable session data.
|
|
308
|
+
|
|
309
|
+
`JsonFileFlexHarnessStores` stores the domains in separate `scopes`, `projections`, `permissions`, `events`, `archives`, and `jobs` directories. Storage and session identifiers are SHA-256 hashed for filenames. It provides:
|
|
310
|
+
|
|
311
|
+
- Strict domain-specific schema validation and optimistic revisions.
|
|
271
312
|
- Static process-wide queues shared by all store instances for the same absolute file.
|
|
272
313
|
- Revision re-reads inside the queue before every save.
|
|
273
|
-
- Atomic temporary-file write and
|
|
314
|
+
- Atomic temporary-file write, file fsync, rename, and parent-directory fsync.
|
|
274
315
|
- Directory mode `0700` and file mode `0600`, including existing paths.
|
|
275
316
|
- Stale temporary-file cleanup and strict snapshot validation.
|
|
276
317
|
|
|
277
|
-
|
|
318
|
+
After every harness using a `JsonFileFlexHarnessStores` instance has been disposed and no store operation remains active, call `await stores.dispose()` to retry and drain any file handle whose earlier close failed. A failed store disposal retains that handle so the call can be retried.
|
|
319
|
+
|
|
320
|
+
The JSON stores are explicitly not cross-process safe. Use a custom `IFlexHarnessStores` implementation backed by a database or another cross-process CAS mechanism when several processes write the same storage namespace.
|
|
278
321
|
|
|
279
|
-
Direct store operations and non-run session mutations surface conflicts as `FlexHarnessStoreConflictError
|
|
322
|
+
Direct store operations and non-run session mutations surface conflicts as `FlexHarnessStoreConflictError` or the corresponding SmartAgent store conflict. Malformed, wrong-schema, or non-JSON snapshots are surfaced as `FlexHarnessStoreFormatError`. A write or deletion that changed its target but cannot confirm parent-directory durability surfaces `FlexHarnessStoreCommitUncertainError` with the affected path, operation, and cause. Run persistence failures cross the external error boundary and therefore become `FlexHarnessExternalError`. FlexHarness does not merge conflicts.
|
|
323
|
+
|
|
324
|
+
Each domain serializes its own mutations. A successful run first persists a hidden completed projection, then finalizes the canonical Agent generation as `accepted`, then promotes the hidden projection publicly. Recovery uses the canonical generation outcome to promote an accepted stage or publish a failed/cancelled projection. Failed and cancelled generations remain auditable but never enter future model context.
|
|
325
|
+
|
|
326
|
+
## Migrating From 2.x
|
|
327
|
+
|
|
328
|
+
Version `3.x` replaces the single `IFlexHarnessStore` snapshot with the split stores above. Run migration while every process that can access the storage namespace is stopped.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import {
|
|
332
|
+
JsonFileFlexHarnessStores,
|
|
333
|
+
} from '@modelprofile.com/flexharness';
|
|
334
|
+
import {
|
|
335
|
+
migrateLegacyFlexHarnessSnapshot,
|
|
336
|
+
type IFlexLegacyHarnessSnapshot,
|
|
337
|
+
} from '@modelprofile.com/flexharness/migration';
|
|
338
|
+
|
|
339
|
+
const storageKey = 'account/project';
|
|
340
|
+
const legacySnapshot: IFlexLegacyHarnessSnapshot = await loadLegacySnapshot(storageKey);
|
|
341
|
+
const stores = new JsonFileFlexHarnessStores({
|
|
342
|
+
directory: '/var/lib/my-app/model-sessions-v3',
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await migrateLegacyFlexHarnessSnapshot(storageKey, legacySnapshot, stores);
|
|
346
|
+
```
|
|
280
347
|
|
|
281
|
-
|
|
348
|
+
`loadLegacySnapshot()` is application-owned access to the snapshot written by the `2.x` store. The migration validates the complete source and every public run before writing. A run left streaming by a process crash is deterministically repaired to the same cancelled state that the `2.x` loader produced in memory. The migration then converts private model messages into generationless canonical Agent conversation events and records terminal SmartAgent transactions for completed, failed, and cancelled public runs. It preflights the scope, projection, permission, Agent event, and job destinations before any write, applies missing per-session domains first, and publishes scope discovery last. It is safe to rerun after no work, a completed prefix, or a complete migration when existing destination content is identical. It fails closed when a destination contains conflicting content or non-empty jobs. Keep the legacy snapshot until the migrated application has loaded and verified every storage namespace.
|
|
282
349
|
|
|
283
350
|
## Shutdown
|
|
284
351
|
|
|
285
352
|
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 its handle is closed; disposal waits for that settlement and reports a late close failure.
|
|
286
353
|
|
|
287
|
-
Tool-handle close settles before a turn can be successful.
|
|
354
|
+
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.
|
|
288
355
|
|
|
289
|
-
`dispose()` is asynchronous and idempotent. It marks the harness closed, prevents operations waiting on persistence from reserving a run, aborts cancellable active runs, rejects pending permissions, waits for committing runs, all run finalizers, state save tails, and tracked detached tool-provider cleanup, then clears listeners and
|
|
356
|
+
`dispose()` is asynchronous and idempotent. It marks the harness closed, prevents operations waiting on persistence from reserving a run, aborts cancellable active runs, rejects pending permissions, waits for committing runs, all run finalizers, state save tails, and tracked detached tool-provider cleanup, then clears listeners. Loaded state caches are cleared after all cleanup succeeds; a failed drain retains its cache and cleanup ownership so a later `dispose()` call can retry it. Multiple run or cleanup failures are reported through `FlexHarnessRunError`.
|
|
290
357
|
|
|
291
358
|
If `dispose()` overlaps a storage namespace already being retired, both calls await the same storage drain and cleanup runs once. A retirement call begun after disposal starts rejects with `FlexHarnessClosedError`.
|
|
292
359
|
|
|
293
|
-
Cancellation is cooperative: model resolvers, tool providers,
|
|
360
|
+
Cancellation is cooperative: model resolvers, tool providers, AgentSession model execution, tools, execution contexts, and cleanup functions must observe the supplied `AbortSignal` and settle tracked work. After a sibling resolver fails, FlexHarness deliberately does not wait for an unresponsive model resolver; a detached tool provider remains tracked because any late handle must be closed. A process host that needs a hard shutdown deadline must enforce that deadline outside FlexHarness and terminate only the process it owns.
|
|
294
361
|
|
|
295
362
|
## License and Legal Information
|
|
296
363
|
|
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: '
|
|
6
|
+
version: '3.0.1',
|
|
7
7
|
description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
|
|
8
8
|
}
|