@modelprofile.com/flexharness 10.11.0 → 10.12.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +14 -2
- package/dist_ts/classes.flexharness.js +340 -57
- package/dist_ts/errors.d.ts +5 -0
- package/dist_ts/errors.js +10 -1
- package/dist_ts/interfaces.d.ts +29 -0
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.json.js +37 -2
- package/dist_ts/utils.sessionmetrics.d.ts +17 -0
- package/dist_ts/utils.sessionmetrics.js +72 -0
- package/package.json +5 -5
- package/readme.md +58 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +378 -53
- package/ts/errors.ts +8 -0
- package/ts/interfaces.ts +31 -0
- package/ts/plugins.ts +3 -2
- package/ts/readme.md +58 -8
- package/ts/utils.json.ts +33 -1
- package/ts/utils.sessionmetrics.ts +104 -0
- package/ts_chat_harness/readme.md +1 -1
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
FlexHarnessSlashCommandReentryError,
|
|
15
15
|
FlexHarnessSlashCommandUnavailableError,
|
|
16
16
|
FlexHarnessSteerRejectedError,
|
|
17
|
+
FlexHarnessStepLimitError,
|
|
17
18
|
FlexHarnessStoreConflictError,
|
|
18
19
|
FlexHarnessStoreCommitUncertainError,
|
|
19
20
|
FlexHarnessValidationError,
|
|
@@ -95,6 +96,8 @@ import type {
|
|
|
95
96
|
IFlexSessionPermissionPolicyContext,
|
|
96
97
|
IFlexSessionActivity,
|
|
97
98
|
IFlexSessionGeneration,
|
|
99
|
+
IFlexSessionMetrics,
|
|
100
|
+
IFlexSessionMetricsState,
|
|
98
101
|
IFlexSessionGenerationCohortEntry,
|
|
99
102
|
IFlexSessionImportHandle,
|
|
100
103
|
IFlexSessionImportLimits,
|
|
@@ -226,6 +229,15 @@ import {
|
|
|
226
229
|
validateUtf8String,
|
|
227
230
|
} from './utils.validation.js';
|
|
228
231
|
|
|
232
|
+
import {
|
|
233
|
+
createSessionMetricsOperation,
|
|
234
|
+
createSessionMetricsState,
|
|
235
|
+
observeSessionMetricsCall,
|
|
236
|
+
sessionMetricsView,
|
|
237
|
+
settleSessionMetricsOperation,
|
|
238
|
+
type ISessionMetricsOperation,
|
|
239
|
+
} from './utils.sessionmetrics.js';
|
|
240
|
+
|
|
229
241
|
type TCanonicalOutcome = 'accepted' | 'rejected' | 'interrupted';
|
|
230
242
|
type TRunPhase =
|
|
231
243
|
| 'admitting'
|
|
@@ -256,6 +268,10 @@ interface IStoredSessionState {
|
|
|
256
268
|
projectionReconciliationRequired: boolean;
|
|
257
269
|
projectionRevision: number;
|
|
258
270
|
projectionQueue: Promise<void>;
|
|
271
|
+
metrics: IFlexSessionMetricsState;
|
|
272
|
+
metricsCompaction?: ISessionMetricsOperation;
|
|
273
|
+
/** Retained independently of the error-neutral projectionQueue. */
|
|
274
|
+
metricsFailure?: unknown;
|
|
259
275
|
rememberedPermissionKeys: Set<string>;
|
|
260
276
|
permissionRevision: number;
|
|
261
277
|
permissionQueue: Promise<void>;
|
|
@@ -402,12 +418,14 @@ interface IActiveRun {
|
|
|
402
418
|
finishedEventEmitted: boolean;
|
|
403
419
|
/** The usage the provider reported for the run's model calls so far. */
|
|
404
420
|
usage: IFlexUsage;
|
|
421
|
+
metricsOperation?: ISessionMetricsOperation;
|
|
405
422
|
reasoningPartIds: Map<string, string>;
|
|
406
423
|
toolPartIds: Map<string, string>;
|
|
407
424
|
trustedToolExecutionErrors: Map<string, Error>;
|
|
408
425
|
subagentCallCount: number;
|
|
409
426
|
subagentSessionIds: Set<string>;
|
|
410
427
|
backgroundSubagents: Set<Promise<void>>;
|
|
428
|
+
cacheDispatch?: plugins.ISmartAiCacheDispatch;
|
|
411
429
|
pendingPermissionIds: Set<string>;
|
|
412
430
|
phase: TRunPhase;
|
|
413
431
|
reservedUserMessage?: IFlexMessage;
|
|
@@ -466,6 +484,7 @@ interface IDelegatedRunAdmissionSeed {
|
|
|
466
484
|
readonly parentQueueId: string;
|
|
467
485
|
readonly parentRunId: string;
|
|
468
486
|
readonly parentToolCallId: string;
|
|
487
|
+
readonly configurationRef?: string;
|
|
469
488
|
readonly agent: string;
|
|
470
489
|
readonly depth: number;
|
|
471
490
|
}
|
|
@@ -672,7 +691,7 @@ const maxSubagentPromptBytes = 64 * 1024;
|
|
|
672
691
|
const maxSubagentTaskIdBytes = 512;
|
|
673
692
|
const maxSubagentPermissionMetadataBytes = 16 * 1024;
|
|
674
693
|
const maxSubagentResultTextBytes = 64 * 1024;
|
|
675
|
-
const defaultMaxSubagentDepth =
|
|
694
|
+
const defaultMaxSubagentDepth = 6;
|
|
676
695
|
const maximumMaxSubagentDepth = 8;
|
|
677
696
|
const defaultMaxSubagentCallsPerRun = 32;
|
|
678
697
|
const maximumMaxSubagentCallsPerRun = 128;
|
|
@@ -1029,6 +1048,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1029
1048
|
scopeId,
|
|
1030
1049
|
resolved.scope.scope,
|
|
1031
1050
|
placeholder.compactorLifecycleController,
|
|
1051
|
+
true,
|
|
1032
1052
|
);
|
|
1033
1053
|
const loaded = await initialization;
|
|
1034
1054
|
if (resolved.state.sessions.get(sessionId) !== placeholder) {
|
|
@@ -1314,6 +1334,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1314
1334
|
await this.stores.projections.save(state.storageKey, sessionId, {
|
|
1315
1335
|
schemaVersion: 3,
|
|
1316
1336
|
revision: 1,
|
|
1337
|
+
metrics: createSessionMetricsState(false),
|
|
1317
1338
|
messages: staged.runs.flatMap((run) => [run.user, run.assistant]),
|
|
1318
1339
|
stagedTerminals: [],
|
|
1319
1340
|
reversionSegments: [],
|
|
@@ -1390,6 +1411,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
1390
1411
|
return publicSnapshot(this.requireSession(state, sessionId).session);
|
|
1391
1412
|
}
|
|
1392
1413
|
|
|
1414
|
+
/** Bounded projection-state read: no message, event, archive or child-history scan. */
|
|
1415
|
+
public async getSessionMetrics(scopeId: string, sessionId: string): Promise<IFlexSessionMetrics> {
|
|
1416
|
+
const { state } = await this.resolveState(scopeId);
|
|
1417
|
+
const stored = this.requireSession(state, sessionId);
|
|
1418
|
+
await stored.projectionQueue;
|
|
1419
|
+
this.assertStateAcceptingWork(state);
|
|
1420
|
+
if (stored.metricsFailure !== undefined) throw stored.metricsFailure;
|
|
1421
|
+
return publicSnapshot(sessionMetricsView(stored.metrics));
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1393
1424
|
public async getSessionReversionInfo(
|
|
1394
1425
|
scopeId: string,
|
|
1395
1426
|
sessionId: string,
|
|
@@ -3014,13 +3045,53 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3014
3045
|
}
|
|
3015
3046
|
throw new FlexHarnessNotFoundError('Prompt queue entry', queueId);
|
|
3016
3047
|
}
|
|
3048
|
+
return this.admitRunSteer(run, steer, prompt, byteSize);
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
/** Sends follow-up input only to an exact running direct child owned by this parent run. */
|
|
3052
|
+
public async sendSubagentInput(
|
|
3053
|
+
scopeId: string,
|
|
3054
|
+
parentSessionId: string,
|
|
3055
|
+
parentRunId: string,
|
|
3056
|
+
taskId: string,
|
|
3057
|
+
steer: IFlexSteerInput,
|
|
3058
|
+
): Promise<IFlexSteerAdmission> {
|
|
3059
|
+
validateIdentifier(parentSessionId, 'parentSessionId');
|
|
3060
|
+
requireTransferIdentifier(parentSessionId, 'parentSessionId');
|
|
3061
|
+
validateIdentifier(parentRunId, 'parentRunId');
|
|
3062
|
+
requireTransferIdentifier(parentRunId, 'parentRunId');
|
|
3063
|
+
this.assertOpen();
|
|
3064
|
+
const { state } = await this.resolveState(scopeId);
|
|
3065
|
+
const parent = state.activeRuns.get(parentSessionId);
|
|
3066
|
+
if (!parent || parent.runId !== parentRunId) {
|
|
3067
|
+
throw new FlexHarnessValidationError('The exact parent run is not active.');
|
|
3068
|
+
}
|
|
3069
|
+
return this.sendSubagentInputInRun(parent, taskId, steer);
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
/** The shared synchronous admission point; neither entrypoint starts or resumes a run. */
|
|
3073
|
+
private admitRunSteer(
|
|
3074
|
+
run: IActiveRun,
|
|
3075
|
+
steer: IFlexSteerInput,
|
|
3076
|
+
prompt: INormalizedFlexPrompt,
|
|
3077
|
+
byteSize: number,
|
|
3078
|
+
): IFlexSteerAdmission {
|
|
3079
|
+
if (
|
|
3080
|
+
run.phase === 'closing'
|
|
3081
|
+
|| run.phase === 'finalizing'
|
|
3082
|
+
|| run.phase === 'promoting'
|
|
3083
|
+
|| run.callbacksClosed
|
|
3084
|
+
|| run.controller.signal.aborted
|
|
3085
|
+
|| run.stop.signal.aborted
|
|
3086
|
+
|| run.state.activeRuns.get(run.sessionId) !== run
|
|
3087
|
+
) throw new FlexHarnessSteerRejectedError('not-running', steer.steerId);
|
|
3017
3088
|
const steering = run.steering;
|
|
3018
3089
|
if (!steering.input.closed && !steering.accepted.has(steer.steerId)) {
|
|
3019
3090
|
if (
|
|
3020
3091
|
steering.accepted.size - steering.applied.size
|
|
3021
3092
|
>= this.promptQueueLimits.maxOutstandingPromptsPerSession
|
|
3022
3093
|
) {
|
|
3023
|
-
throw new FlexHarnessQueueFullError(`Session "${sessionId}" has reached its pending steer limit.`);
|
|
3094
|
+
throw new FlexHarnessQueueFullError(`Session "${run.sessionId}" has reached its pending steer limit.`);
|
|
3024
3095
|
}
|
|
3025
3096
|
// Pending steers hold outstanding prompt bytes of the session until they are taken in.
|
|
3026
3097
|
if (
|
|
@@ -3028,7 +3099,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3028
3099
|
> this.promptQueueLimits.maxOutstandingBytesPerSession
|
|
3029
3100
|
) {
|
|
3030
3101
|
throw new FlexHarnessQueueFullError(
|
|
3031
|
-
`Session "${sessionId}" has reached its outstanding prompt byte limit.`,
|
|
3102
|
+
`Session "${run.sessionId}" has reached its outstanding prompt byte limit.`,
|
|
3032
3103
|
);
|
|
3033
3104
|
}
|
|
3034
3105
|
}
|
|
@@ -3052,11 +3123,130 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3052
3123
|
steering.stored.outstandingPromptBytes += byteSize;
|
|
3053
3124
|
this.emitEvent(run.scopeId, run.stored.session, {
|
|
3054
3125
|
type: 'prompt.steer.accepted',
|
|
3055
|
-
queueId,
|
|
3126
|
+
queueId: run.queueId,
|
|
3056
3127
|
runId: run.runId,
|
|
3057
3128
|
steerId: steer.steerId,
|
|
3058
3129
|
});
|
|
3059
|
-
return { steerId: steer.steerId, queueId, runId: run.runId };
|
|
3130
|
+
return { steerId: steer.steerId, queueId: run.queueId, runId: run.runId };
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
private sendSubagentInputInRun(
|
|
3134
|
+
parent: IActiveRun,
|
|
3135
|
+
taskId: string,
|
|
3136
|
+
steer: IFlexSteerInput,
|
|
3137
|
+
): IFlexSteerAdmission {
|
|
3138
|
+
this.assertOpen();
|
|
3139
|
+
validateUtf8String(taskId, 'taskId', maxSubagentTaskIdBytes, true);
|
|
3140
|
+
validateIdentifier(steer.steerId, 'steerId');
|
|
3141
|
+
requireTransferIdentifier(steer.steerId, 'steerId');
|
|
3142
|
+
const prompt = normalizeFlexPrompt(steer.prompt);
|
|
3143
|
+
const byteSize = this.promptAdmissionByteSize(steer.prompt, {});
|
|
3144
|
+
this.assertStateAcceptingWork(parent.state);
|
|
3145
|
+
const child = parent.state.activeRuns.get(taskId);
|
|
3146
|
+
if (!child) throw new FlexHarnessSteerRejectedError('not-running', steer.steerId);
|
|
3147
|
+
this.assertLiveSubagentOwnership(child, parent);
|
|
3148
|
+
return this.admitRunSteer(child, steer, prompt, byteSize);
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
/** Fences live ownership by exact runtime pointers and admission seeds all the way to root. */
|
|
3152
|
+
private assertLiveSubagentOwnership(child: IActiveRun, parent: IActiveRun): void {
|
|
3153
|
+
const state = parent.state;
|
|
3154
|
+
const visited = new Set<IActiveRun>();
|
|
3155
|
+
let current = child;
|
|
3156
|
+
let expectedParent: IActiveRun | undefined = parent;
|
|
3157
|
+
while (true) {
|
|
3158
|
+
const stored = current.stored;
|
|
3159
|
+
const session = stored.session;
|
|
3160
|
+
const queued = stored.outstandingPromptsById.get(current.queueId);
|
|
3161
|
+
if (
|
|
3162
|
+
visited.has(current)
|
|
3163
|
+
|| current.state !== state
|
|
3164
|
+
|| state.activeRuns.get(current.sessionId) !== current
|
|
3165
|
+
|| state.sessions.get(current.sessionId) !== stored
|
|
3166
|
+
|| session.sessionId !== current.sessionId
|
|
3167
|
+
|| state.tombstones.has(current.sessionId)
|
|
3168
|
+
|| state.initializingSessions.has(current.sessionId)
|
|
3169
|
+
|| current.phase !== 'running'
|
|
3170
|
+
|| current.callbacksClosed
|
|
3171
|
+
|| current.controller.signal.aborted
|
|
3172
|
+
|| current.stop.signal.aborted
|
|
3173
|
+
|| current.steering.input.closed
|
|
3174
|
+
|| !queued
|
|
3175
|
+
|| queued.state !== state
|
|
3176
|
+
|| queued.stored !== stored
|
|
3177
|
+
|| queued.sessionId !== current.sessionId
|
|
3178
|
+
|| queued.queueId !== current.queueId
|
|
3179
|
+
|| queued.runId !== current.runId
|
|
3180
|
+
|| queued.steering !== current.steering
|
|
3181
|
+
) throw new FlexHarnessValidationError('The subagent or ancestor run is no longer live and owned.');
|
|
3182
|
+
this.assertSessionWritable(stored);
|
|
3183
|
+
visited.add(current);
|
|
3184
|
+
if (session.parentSessionId === undefined) {
|
|
3185
|
+
if (expectedParent !== undefined || queued.delegationSeed !== undefined) {
|
|
3186
|
+
throw new FlexHarnessValidationError('The task is not a direct child of this parent run.');
|
|
3187
|
+
}
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
const seed = queued.delegationSeed;
|
|
3191
|
+
if (!seed) throw new FlexHarnessValidationError('The child has no live delegated admission owner.');
|
|
3192
|
+
const owner = current.delegatedRunAdmissionOwner;
|
|
3193
|
+
const owningParent = seed.parentRun;
|
|
3194
|
+
const parentSession = owningParent.stored.session;
|
|
3195
|
+
if (
|
|
3196
|
+
(expectedParent !== undefined && owningParent !== expectedParent)
|
|
3197
|
+
|| seed.state !== state
|
|
3198
|
+
|| seed.storageKey !== state.storageKey
|
|
3199
|
+
|| seed.childStored !== stored
|
|
3200
|
+
|| stored.storageKey !== seed.storageKey
|
|
3201
|
+
|| seed.sessionId !== current.sessionId
|
|
3202
|
+
|| session.sessionId !== seed.sessionId
|
|
3203
|
+
|| session.sessionGenerationId !== seed.sessionGenerationId
|
|
3204
|
+
|| session.sessionGenerationSequence !== seed.sessionGenerationSequence
|
|
3205
|
+
|| session.parentSessionId !== seed.parentSessionId
|
|
3206
|
+
|| session.parentRunId !== seed.originParentRunId
|
|
3207
|
+
|| session.parentToolCallId !== seed.originParentToolCallId
|
|
3208
|
+
|| session.agent !== seed.agent
|
|
3209
|
+
|| session.depth !== seed.depth
|
|
3210
|
+
|| session.configurationRef !== seed.configurationRef
|
|
3211
|
+
|| parentSession.configurationRef !== seed.configurationRef
|
|
3212
|
+
|| seed.depth !== (parentSession.depth ?? 0) + 1
|
|
3213
|
+
|| !owningParent.subagents.has(seed.agent)
|
|
3214
|
+
|| seed.parentStored !== owningParent.stored
|
|
3215
|
+
|| seed.parentStored.storageKey !== seed.storageKey
|
|
3216
|
+
|| owningParent.sessionId !== seed.parentSessionId
|
|
3217
|
+
|| owningParent.runId !== seed.parentRunId
|
|
3218
|
+
|| owningParent.queueId !== seed.parentQueueId
|
|
3219
|
+
|| parentSession.sessionGenerationId !== seed.parentSessionGenerationId
|
|
3220
|
+
|| parentSession.sessionGenerationSequence !== seed.parentSessionGenerationSequence
|
|
3221
|
+
|| current.scopeId !== seed.scopeId
|
|
3222
|
+
|| current.scope !== seed.scope
|
|
3223
|
+
|| owningParent.scopeId !== seed.scopeId
|
|
3224
|
+
|| owningParent.scope !== seed.scope
|
|
3225
|
+
|| (this.delegatedRunAdmissionProvider !== undefined && owner === undefined)
|
|
3226
|
+
|| (owner !== undefined && (
|
|
3227
|
+
owner.storageState !== state || owner.run !== current
|
|
3228
|
+
|| !state.delegatedRunAdmissionOwners.has(owner)
|
|
3229
|
+
|| owner.status !== 'acquired' || !owner.acquisitionSettled || owner.acquisitionDetached
|
|
3230
|
+
|| owner.context.runId !== current.runId || owner.context.queueId !== current.queueId
|
|
3231
|
+
|| owner.context.parentRunId !== owningParent.runId
|
|
3232
|
+
|| owner.context.parentQueueId !== owningParent.queueId
|
|
3233
|
+
|| owner.context.scopeId !== seed.scopeId || owner.context.scope !== seed.scope
|
|
3234
|
+
|| owner.context.storageKey !== seed.storageKey
|
|
3235
|
+
|| owner.context.sessionId !== seed.sessionId
|
|
3236
|
+
|| owner.context.parentSessionId !== seed.parentSessionId
|
|
3237
|
+
|| owner.context.parentToolCallId !== seed.parentToolCallId
|
|
3238
|
+
|| owner.context.originParentRunId !== seed.originParentRunId
|
|
3239
|
+
|| owner.context.originParentToolCallId !== seed.originParentToolCallId
|
|
3240
|
+
|| owner.context.agent !== seed.agent || owner.context.depth !== seed.depth
|
|
3241
|
+
|| owner.context.sessionGenerationId !== seed.sessionGenerationId
|
|
3242
|
+
|| owner.context.sessionGenerationSequence !== seed.sessionGenerationSequence
|
|
3243
|
+
|| owner.context.parentSessionGenerationId !== seed.parentSessionGenerationId
|
|
3244
|
+
|| owner.context.parentSessionGenerationSequence !== seed.parentSessionGenerationSequence
|
|
3245
|
+
))
|
|
3246
|
+
) throw new FlexHarnessValidationError('The task is not owned by this exact parent run and configuration.');
|
|
3247
|
+
current = owningParent;
|
|
3248
|
+
expectedParent = undefined;
|
|
3249
|
+
}
|
|
3060
3250
|
}
|
|
3061
3251
|
|
|
3062
3252
|
public async abort(scopeId: string, sessionId: string): Promise<boolean> {
|
|
@@ -4532,7 +4722,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4532
4722
|
this.assertPromptPromotion(queued, run);
|
|
4533
4723
|
const reservation = this.createReservation(run, prompt);
|
|
4534
4724
|
closeReservationPublication = this.openPublication(run.state, run.sessionId);
|
|
4725
|
+
run.metricsOperation = createSessionMetricsOperation(run.runId, 'generation');
|
|
4535
4726
|
await this.mutateProjection(run.state, run.stored, () => {
|
|
4727
|
+
if (run.stored.metrics.pendingGenerationId !== undefined) {
|
|
4728
|
+
throw new Error('Session metrics already owns a generation.');
|
|
4729
|
+
}
|
|
4730
|
+
run.stored.metrics.pendingGenerationId = run.runId;
|
|
4536
4731
|
run.stored.messages.push(reservation.userMessage, reservation.assistantMessage);
|
|
4537
4732
|
if (run.stored.session.agent === undefined) {
|
|
4538
4733
|
this.pruneReversionState(run.stored, true);
|
|
@@ -4650,6 +4845,8 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4650
4845
|
prepare: (context: { generationId: string; abortSignal: AbortSignal }) =>
|
|
4651
4846
|
this.prepareGeneration(run, options, context.abortSignal),
|
|
4652
4847
|
pendingInput: run.steering.input,
|
|
4848
|
+
activateOnBackgroundJobs: true,
|
|
4849
|
+
onCacheDispatch: (dispatch: plugins.ISmartAiCacheDispatch) => { run.cacheDispatch = dispatch; },
|
|
4653
4850
|
onInputApplied: (input: plugins.IAgentAppliedInput) => this.applyRunSteer(run, input),
|
|
4654
4851
|
beforeFinish: (context: { abortSignal: AbortSignal }) =>
|
|
4655
4852
|
this.awaitNextBackgroundSubagent(run, context.abortSignal),
|
|
@@ -4803,7 +5000,8 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4803
5000
|
/**
|
|
4804
5001
|
* Holds the end of a run's generation until one more of its background subagents settles. The
|
|
4805
5002
|
* subagent commits its result to the generation before it settles, so the model takes the result
|
|
4806
|
-
* in with another step instead of the run ending without it.
|
|
5003
|
+
* in with another step instead of the run ending without it. The engine bounds the scoped
|
|
5004
|
+
* wait from its observed dispatch and commits timer check-ins. Returns at once when none runs.
|
|
4807
5005
|
*/
|
|
4808
5006
|
private async awaitNextBackgroundSubagent(run: IActiveRun, signal: AbortSignal): Promise<void> {
|
|
4809
5007
|
if (run.backgroundSubagents.size === 0) return;
|
|
@@ -4828,8 +5026,16 @@ export class FlexHarness<TScope = unknown> {
|
|
|
4828
5026
|
try {
|
|
4829
5027
|
// The transactional generation settles only after its model calls are reported, so
|
|
4830
5028
|
// `run.usage` is complete once it settles, also for a cancelled run.
|
|
4831
|
-
|
|
4832
|
-
|
|
5029
|
+
let rawResult: plugins.IAgentGenerateResult;
|
|
5030
|
+
try {
|
|
5031
|
+
rawResult = await generation;
|
|
5032
|
+
generated = true;
|
|
5033
|
+
} finally {
|
|
5034
|
+
await this.settleMetricsOperation(run.state, run.stored, run.metricsOperation!);
|
|
5035
|
+
}
|
|
5036
|
+
if (rawResult.terminationReason === 'step-limit') {
|
|
5037
|
+
throw this.trustInternalError(new FlexHarnessStepLimitError(rawResult.steps));
|
|
5038
|
+
}
|
|
4833
5039
|
// Background tools do not block inference, but the owning turn joins its admitted children
|
|
4834
5040
|
// before releasing its authority or committing its terminal projection.
|
|
4835
5041
|
await Promise.all([...run.backgroundSubagents]);
|
|
@@ -5644,6 +5850,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5644
5850
|
const close = toolHandle?.close
|
|
5645
5851
|
? async () => {
|
|
5646
5852
|
if (closed) return;
|
|
5853
|
+
// Inference has ended before its tool lease closes. Fence follow-up admission
|
|
5854
|
+
// synchronously, even when external cleanup has not settled yet.
|
|
5855
|
+
run.steering.input.close();
|
|
5647
5856
|
closePromise ??= Promise.resolve()
|
|
5648
5857
|
.then(() => toolHandle!.close!())
|
|
5649
5858
|
.then(() => {
|
|
@@ -5684,6 +5893,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5684
5893
|
if (run.subagents.size > 0 && (run.stored.session.depth ?? 0) < this.maxSubagentDepth) {
|
|
5685
5894
|
names.add('delegate');
|
|
5686
5895
|
names.add('delegate_result');
|
|
5896
|
+
names.add('delegate_send');
|
|
5687
5897
|
}
|
|
5688
5898
|
if (this.builtInTools.renameSession) names.add('rename_session');
|
|
5689
5899
|
const projectManagement = this.builtInTools.projectManagement;
|
|
@@ -5708,6 +5918,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5708
5918
|
if (run.subagents.size > 0 && (run.stored.session.depth ?? 0) < this.maxSubagentDepth) {
|
|
5709
5919
|
tools.delegate = this.createSubagentTool(run);
|
|
5710
5920
|
tools.delegate_result = this.createSubagentResultTool(run);
|
|
5921
|
+
tools.delegate_send = this.createSubagentSendTool(run);
|
|
5711
5922
|
}
|
|
5712
5923
|
if (this.builtInTools.renameSession) tools.rename_session = this.createRenameSessionTool(run);
|
|
5713
5924
|
const projectManagement = this.builtInTools.projectManagement;
|
|
@@ -6253,9 +6464,33 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6253
6464
|
}
|
|
6254
6465
|
}
|
|
6255
6466
|
|
|
6467
|
+
private createSubagentSendTool(run: IActiveRun): unknown {
|
|
6468
|
+
return plugins.tool({
|
|
6469
|
+
description: 'Send follow-up instructions to a RUNNING direct child owned by this exact parent turn. The child takes the message at its next inference boundary; running tools and permission waits are not interrupted. Never starts or resumes a settled child. Use delegate_result for progress and results.',
|
|
6470
|
+
inputSchema: plugins.z.object({
|
|
6471
|
+
taskId: plugins.z.string().min(1).max(maxSubagentTaskIdBytes),
|
|
6472
|
+
prompt: plugins.z.string().min(1).max(maxSubagentPromptBytes),
|
|
6473
|
+
}).strict(),
|
|
6474
|
+
execute: async (
|
|
6475
|
+
{ taskId, prompt }: { taskId: string; prompt: string },
|
|
6476
|
+
options?: { toolCallId?: string },
|
|
6477
|
+
) => {
|
|
6478
|
+
validateUtf8String(prompt, 'delegate_send prompt', maxSubagentPromptBytes, true);
|
|
6479
|
+
const toolCallId = options?.toolCallId;
|
|
6480
|
+
validateUtf8String(toolCallId, 'delegate_send toolCallId', maxTransferIdentifierBytes, true);
|
|
6481
|
+
return this.sendSubagentInputInRun(run, taskId, {
|
|
6482
|
+
steerId: `steer_${sha256Hex(JSON.stringify([
|
|
6483
|
+
'flexharness-subagent-input-v1', run.state.storageKey, run.sessionId, run.runId, toolCallId,
|
|
6484
|
+
]))}`,
|
|
6485
|
+
prompt,
|
|
6486
|
+
});
|
|
6487
|
+
},
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6256
6491
|
private createSubagentResultTool(run: IActiveRun): unknown {
|
|
6257
6492
|
return plugins.tool({
|
|
6258
|
-
description: 'Read a subagent owned by this parent. Returns live status and the final text when settled. waitMs optionally waits for completion, bounded to 240 seconds; omit it to continue immediately.',
|
|
6493
|
+
description: 'Read a subagent owned by this parent. Returns live status and the final text when settled. waitMs optionally waits for completion, bounded to 240 seconds and the remaining configured cache idle budget; omit it to continue immediately.',
|
|
6259
6494
|
inputSchema: plugins.z.object({
|
|
6260
6495
|
taskId: plugins.z.string().min(1).max(maxSubagentTaskIdBytes),
|
|
6261
6496
|
waitMs: plugins.z.number().int().min(0).max(240_000).optional(),
|
|
@@ -6274,7 +6509,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6274
6509
|
await Promise.race([
|
|
6275
6510
|
active.completion.catch(() => undefined),
|
|
6276
6511
|
new Promise<void>((resolve, reject) => {
|
|
6277
|
-
timer = setTimeout(resolve, waitMs);
|
|
6512
|
+
timer = setTimeout(resolve, plugins.getSmartAiCacheWaitMs(waitMs, run.cacheDispatch));
|
|
6278
6513
|
onAbort = () => reject(run.controller.signal.reason);
|
|
6279
6514
|
run.controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
6280
6515
|
if (run.controller.signal.aborted) onAbort();
|
|
@@ -6328,6 +6563,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6328
6563
|
parentQueueId: run.queueId,
|
|
6329
6564
|
parentRunId: run.runId,
|
|
6330
6565
|
parentToolCallId: toolCallId,
|
|
6566
|
+
...(run.stored.session.configurationRef === undefined
|
|
6567
|
+
? {}
|
|
6568
|
+
: { configurationRef: run.stored.session.configurationRef }),
|
|
6331
6569
|
agent: definition.name,
|
|
6332
6570
|
depth: (run.stored.session.depth ?? 0) + 1,
|
|
6333
6571
|
});
|
|
@@ -6467,6 +6705,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6467
6705
|
run.scopeId,
|
|
6468
6706
|
run.scope as TScope,
|
|
6469
6707
|
placeholder!.compactorLifecycleController,
|
|
6708
|
+
true,
|
|
6470
6709
|
);
|
|
6471
6710
|
const parentLostOwnership = run.controller.signal.aborted
|
|
6472
6711
|
|| state.sessions.get(run.sessionId) !== run.stored
|
|
@@ -8289,34 +8528,59 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8289
8528
|
stored: IStoredSessionState,
|
|
8290
8529
|
event: plugins.TAgentModelCallUsageEvent,
|
|
8291
8530
|
): void {
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8531
|
+
try {
|
|
8532
|
+
const { source, generationId, ...call } = event;
|
|
8533
|
+
const sessionId = stored.session.sessionId;
|
|
8534
|
+
const activeRun = state.activeRuns.get(sessionId);
|
|
8535
|
+
const run = generationId !== undefined && activeRun?.runId === generationId ? activeRun : undefined;
|
|
8536
|
+
const operation = source === 'compaction' ? stored.metricsCompaction : run?.metricsOperation;
|
|
8537
|
+
if (!operation) throw new Error('Model call usage has no owning metrics operation.');
|
|
8538
|
+
observeSessionMetricsCall(operation, call);
|
|
8539
|
+
let scopeId = run?.scopeId;
|
|
8540
|
+
if (scopeId === undefined) {
|
|
8541
|
+
const ambientContext = this.compactorInvocationContext.getStore();
|
|
8542
|
+
const invocationContext = ambientContext
|
|
8543
|
+
? this.deferredCompactorContexts.get(ambientContext) ?? ambientContext
|
|
8544
|
+
: undefined;
|
|
8545
|
+
if (
|
|
8546
|
+
!invocationContext
|
|
8547
|
+
|| invocationContext.storageKey !== state.storageKey
|
|
8548
|
+
|| invocationContext.sessionId !== sessionId
|
|
8549
|
+
) {
|
|
8550
|
+
throw new Error('Model call usage is missing its exact FlexHarness invocation context.');
|
|
8551
|
+
}
|
|
8552
|
+
scopeId = invocationContext.scopeId;
|
|
8553
|
+
}
|
|
8554
|
+
if (run && call.status === 'reported') {
|
|
8555
|
+
for (const key of Object.keys(run.usage) as (keyof IFlexUsage)[]) run.usage[key] += call.usage[key];
|
|
8308
8556
|
}
|
|
8309
|
-
scopeId
|
|
8557
|
+
this.emitEvent(scopeId, stored.session, {
|
|
8558
|
+
type: 'model.usage',
|
|
8559
|
+
...(generationId === undefined ? {} : { runId: generationId }),
|
|
8560
|
+
source,
|
|
8561
|
+
...call,
|
|
8562
|
+
});
|
|
8563
|
+
} catch (error) {
|
|
8564
|
+
// AgentSession isolates the entire callback, including event/context failures. Join it later.
|
|
8565
|
+
stored.metricsFailure ??= error;
|
|
8566
|
+
throw error;
|
|
8310
8567
|
}
|
|
8311
|
-
|
|
8312
|
-
|
|
8568
|
+
}
|
|
8569
|
+
|
|
8570
|
+
private async settleMetricsOperation(
|
|
8571
|
+
state: IStorageState,
|
|
8572
|
+
stored: IStoredSessionState,
|
|
8573
|
+
operation: ISessionMetricsOperation,
|
|
8574
|
+
): Promise<void> {
|
|
8575
|
+
try {
|
|
8576
|
+
if (stored.metricsFailure !== undefined) throw stored.metricsFailure;
|
|
8577
|
+
await this.mutateProjection(state, stored, () => settleSessionMetricsOperation(stored.metrics, operation), true);
|
|
8578
|
+
} catch (error) {
|
|
8579
|
+
stored.metricsFailure ??= error;
|
|
8580
|
+
// Never allow a later operation to overwrite a marker whose spend was not durably joined.
|
|
8581
|
+
state.lifecycle = 'fenced';
|
|
8582
|
+
throw error;
|
|
8313
8583
|
}
|
|
8314
|
-
this.emitEvent(scopeId, stored.session, {
|
|
8315
|
-
type: 'model.usage',
|
|
8316
|
-
...(generationId === undefined ? {} : { runId: generationId }),
|
|
8317
|
-
source,
|
|
8318
|
-
...call,
|
|
8319
|
-
});
|
|
8320
8584
|
}
|
|
8321
8585
|
|
|
8322
8586
|
private callbackRun(state: IStorageState, sessionId: string): IActiveRun | undefined {
|
|
@@ -8820,9 +9084,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8820
9084
|
errors.push(this.projectExternalError(run, error, 'persistence'));
|
|
8821
9085
|
}
|
|
8822
9086
|
}
|
|
8823
|
-
if (projectionReserved) {
|
|
9087
|
+
if (projectionReserved || run.stored.metrics.pendingGenerationId === run.runId) {
|
|
8824
9088
|
try {
|
|
8825
9089
|
await this.mutateProjection(run.state, run.stored, () => {
|
|
9090
|
+
if (run.stored.metrics.pendingGenerationId === run.runId) {
|
|
9091
|
+
delete run.stored.metrics.pendingGenerationId;
|
|
9092
|
+
}
|
|
8826
9093
|
run.stored.messages = run.stored.messages.filter((message) => message.runId !== run.runId);
|
|
8827
9094
|
run.stored.stagedTerminals = run.stored.stagedTerminals.filter((entry) => entry.runId !== run.runId);
|
|
8828
9095
|
run.stored.reversionSegments = run.stored.reversionSegments.filter(
|
|
@@ -9403,6 +9670,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9403
9670
|
projectionReconciliationRequired: false,
|
|
9404
9671
|
projectionRevision: 0,
|
|
9405
9672
|
projectionQueue: Promise.resolve(),
|
|
9673
|
+
metrics: createSessionMetricsState(false),
|
|
9406
9674
|
rememberedPermissionKeys: new Set(),
|
|
9407
9675
|
permissionRevision: 0,
|
|
9408
9676
|
permissionQueue: Promise.resolve(),
|
|
@@ -9427,6 +9695,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9427
9695
|
scopeId: string,
|
|
9428
9696
|
scope: TScope,
|
|
9429
9697
|
compactorLifecycleController = new AbortController(),
|
|
9698
|
+
newSession = false,
|
|
9430
9699
|
): Promise<IStoredSessionState> {
|
|
9431
9700
|
const sessionId = metadata.sessionId;
|
|
9432
9701
|
const compactorContext = this.createCompactorContext(
|
|
@@ -9530,11 +9799,12 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9530
9799
|
pendingReversionReleases,
|
|
9531
9800
|
projectionSchemaVersion: projection?.schemaVersion ?? 3,
|
|
9532
9801
|
projectionReconciliationRequired: false,
|
|
9533
|
-
...(projection
|
|
9802
|
+
...(projection
|
|
9534
9803
|
? { projectionBaseline: cloneSerializable(projection) }
|
|
9535
9804
|
: {}),
|
|
9536
9805
|
projectionRevision: projection?.revision ?? 0,
|
|
9537
9806
|
projectionQueue: Promise.resolve(),
|
|
9807
|
+
metrics: cloneSerializable(currentProjection?.metrics ?? createSessionMetricsState(newSession)),
|
|
9538
9808
|
rememberedPermissionKeys: new Set(permission?.rememberedPermissionKeys ?? []),
|
|
9539
9809
|
permissionRevision: permission?.revision ?? 0,
|
|
9540
9810
|
permissionQueue: Promise.resolve(),
|
|
@@ -9553,6 +9823,19 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9553
9823
|
terminalPromptQueueEntries: new Map(),
|
|
9554
9824
|
outstandingPromptBytes: 0,
|
|
9555
9825
|
};
|
|
9826
|
+
if (currentProjection?.metrics === undefined
|
|
9827
|
+
|| stored.metrics.pendingGenerationId !== undefined
|
|
9828
|
+
|| stored.metrics.pendingCompactionId !== undefined
|
|
9829
|
+
|| (metadata.origin !== undefined && stored.metrics.lifetimeUsageComplete)) {
|
|
9830
|
+
await this.mutateProjection(state, stored, () => {
|
|
9831
|
+
if (stored.metrics.pendingGenerationId !== undefined || stored.metrics.pendingCompactionId !== undefined
|
|
9832
|
+
|| metadata.origin !== undefined) {
|
|
9833
|
+
stored.metrics.lifetimeUsageComplete = false;
|
|
9834
|
+
}
|
|
9835
|
+
delete stored.metrics.pendingGenerationId;
|
|
9836
|
+
delete stored.metrics.pendingCompactionId;
|
|
9837
|
+
});
|
|
9838
|
+
}
|
|
9556
9839
|
const {
|
|
9557
9840
|
contextCompactor,
|
|
9558
9841
|
...agentSessionPolicy
|
|
@@ -9609,22 +9892,55 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9609
9892
|
events: filteredEvents,
|
|
9610
9893
|
}),
|
|
9611
9894
|
);
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9895
|
+
const operation = createSessionMetricsOperation(plugins.crypto.randomUUID(), 'compaction');
|
|
9896
|
+
try {
|
|
9897
|
+
await this.mutateProjection(state, stored, () => {
|
|
9898
|
+
if (stored.metrics.pendingCompactionId !== undefined || stored.metricsFailure !== undefined) {
|
|
9899
|
+
throw new Error('Session metrics compaction ownership is unresolved.');
|
|
9900
|
+
}
|
|
9901
|
+
stored.metrics.pendingCompactionId = operation.id;
|
|
9902
|
+
});
|
|
9903
|
+
} catch (error) {
|
|
9904
|
+
// No compactor ran. Reconcile a known committed reservation without inventing spend.
|
|
9905
|
+
// Unknown outcomes remain fenced and are repaired conservatively on namespace reload.
|
|
9906
|
+
if (state.lifecycle !== 'fenced' && stored.metrics.pendingCompactionId === operation.id) {
|
|
9907
|
+
try {
|
|
9908
|
+
await this.mutateProjection(state, stored, () => {
|
|
9909
|
+
delete stored.metrics.pendingCompactionId;
|
|
9910
|
+
}, true);
|
|
9911
|
+
} catch (cleanupError) {
|
|
9912
|
+
stored.metricsFailure ??= cleanupError;
|
|
9913
|
+
state.lifecycle = 'fenced';
|
|
9914
|
+
throw combineErrors([error, cleanupError]);
|
|
9915
|
+
}
|
|
9916
|
+
}
|
|
9917
|
+
throw error;
|
|
9918
|
+
}
|
|
9919
|
+
stored.metricsCompaction = operation;
|
|
9920
|
+
try {
|
|
9921
|
+
return await contextCompactor(filteredMessages, filteredEvents, {
|
|
9922
|
+
...options,
|
|
9923
|
+
...invocation,
|
|
9924
|
+
scope: invocation.scope as TScope,
|
|
9925
|
+
...this.compactionModel(state, sessionId, ambientContext, options.reason, modelHint),
|
|
9926
|
+
abortSignal: options.abortSignal
|
|
9927
|
+
? AbortSignal.any([
|
|
9928
|
+
options.abortSignal,
|
|
9929
|
+
state.compactorLifecycleController.signal,
|
|
9930
|
+
stored.compactorLifecycleController.signal,
|
|
9931
|
+
])
|
|
9932
|
+
: AbortSignal.any([
|
|
9933
|
+
state.compactorLifecycleController.signal,
|
|
9934
|
+
stored.compactorLifecycleController.signal,
|
|
9935
|
+
]),
|
|
9936
|
+
});
|
|
9937
|
+
} finally {
|
|
9938
|
+
try {
|
|
9939
|
+
await this.settleMetricsOperation(state, stored, operation);
|
|
9940
|
+
} finally {
|
|
9941
|
+
delete stored.metricsCompaction;
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
9628
9944
|
},
|
|
9629
9945
|
}
|
|
9630
9946
|
: {}),
|
|
@@ -10044,6 +10360,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
10044
10360
|
const beforeBaseline = stored.projectionBaseline === undefined
|
|
10045
10361
|
? undefined
|
|
10046
10362
|
: cloneSerializable(stored.projectionBaseline);
|
|
10363
|
+
const beforeMetrics = cloneSerializable(stored.metrics);
|
|
10047
10364
|
const beforeMessages = cloneSerializable(stored.messages);
|
|
10048
10365
|
const beforeStages = cloneSerializable(stored.stagedTerminals);
|
|
10049
10366
|
const beforeSegments = cloneSerializable(stored.reversionSegments);
|
|
@@ -10056,6 +10373,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
10056
10373
|
const beforeSnapshot: TFlexProjectionSnapshot = beforeBaseline ?? {
|
|
10057
10374
|
schemaVersion: 3,
|
|
10058
10375
|
revision: beforeRevision,
|
|
10376
|
+
metrics: cloneSerializable(beforeMetrics),
|
|
10059
10377
|
messages: cloneSerializable(beforeMessages),
|
|
10060
10378
|
stagedTerminals: cloneSerializable(beforeStages),
|
|
10061
10379
|
reversionSegments: cloneSerializable(beforeSegments),
|
|
@@ -10072,6 +10390,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
10072
10390
|
if (beforeBaseline === undefined) delete stored.projectionBaseline;
|
|
10073
10391
|
else stored.projectionBaseline = beforeBaseline;
|
|
10074
10392
|
stored.projectionRevision = beforeRevision;
|
|
10393
|
+
stored.metrics = beforeMetrics;
|
|
10075
10394
|
stored.messages = beforeMessages;
|
|
10076
10395
|
stored.stagedTerminals = beforeStages;
|
|
10077
10396
|
stored.reversionSegments = beforeSegments;
|
|
@@ -10251,6 +10570,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
10251
10570
|
return {
|
|
10252
10571
|
schemaVersion: 3,
|
|
10253
10572
|
revision,
|
|
10573
|
+
metrics: cloneSerializable(stored.metrics),
|
|
10254
10574
|
messages: cloneSerializable(stored.messages),
|
|
10255
10575
|
stagedTerminals: cloneSerializable(stored.stagedTerminals),
|
|
10256
10576
|
reversionSegments: cloneSerializable(stored.reversionSegments),
|
|
@@ -10962,6 +11282,11 @@ export class FlexHarness<TScope = unknown> {
|
|
|
10962
11282
|
errors.push(error);
|
|
10963
11283
|
}
|
|
10964
11284
|
}
|
|
11285
|
+
if (stored.metricsFailure !== undefined) {
|
|
11286
|
+
errors.push(stored.metricsFailure);
|
|
11287
|
+
// Runtime ownership has settled; the fenced namespace will be reloaded from its durable WAL.
|
|
11288
|
+
if (stored.agentSessionCloseCompleted) delete stored.metricsFailure;
|
|
11289
|
+
}
|
|
10965
11290
|
if (errors.length > 0) throw combineErrors(errors);
|
|
10966
11291
|
}
|
|
10967
11292
|
|