@agen-ai/agent-runtime 0.4.2 → 0.4.3

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
@@ -9,7 +9,7 @@ sessions; each returned session owns its binding and conversation-local operatio
9
9
  The runtime depends only on `@agen-ai/agent-protocol`. It has no concept of tenants, SaaS
10
10
  workspaces, assigned users, database rows, persistence sequence, visibility, billing, host boots,
11
11
  distributed leases, or storage policy. A host must authorize and select an instance before calling this SPI.
12
- This unpublished source candidate implements Agent Protocol V12 at coordinated SDK version `0.4.2`,
12
+ This package implements Agent Protocol V12 at coordinated SDK version `0.4.3`,
13
13
  including tagged decision responses and exact turn request unavailability. Published `0.4.1`
14
14
  retains V11 and its qualification evidence; the earlier `0.4.0` release retains V10.
15
15
  The runtime also provides neutral session forms, plan reviews, staged native continuations and
@@ -157,10 +157,11 @@ reopen an older request. A host must cancel human decisions when their execution
157
157
  those tasks during cleanup. Keep the session or exact opening-cleanup receipt if cleanup fails;
158
158
  disposing its registry or host resources before quiescence would lose the retained owner.
159
159
 
160
- `runtime.retrying` reports a retry already owned by the provider on the active turn. It preserves
160
+ `runtime.retrying` reports a retry already owned by the provider on the active turn or operation. It preserves
161
161
  turn ownership, pending requests, and per-output backpressure; it neither starts another execution
162
162
  nor completes the turn. It is rejected before `turn.started`, after `turn.completed`, after a
163
- `final_diff` terminal materialization, and from session-operation observations. The event's neutral
163
+ `final_diff` terminal materialization on a turn. Current source also admits it through an owned
164
+ session operation's awaited `onOutput`, with the exact `observationTurnId` and no `turn.started`. The event's neutral
164
165
  failure category and recovery hint describe the observed condition. A recovery hint never authorizes
165
166
  replaying work or changes an uncertain execution outcome. Contract failures, delegated-operation
166
167
  started evidence, and retained cleanup responsibilities remain separate from those failure values.
@@ -188,6 +189,23 @@ provider-delegated steering failures throw `AgentProviderDelegatedOperationError
188
189
  cause and explicit started-execution evidence so a host can retire the unusable session. Platform
189
190
  scheduling and provider-native queue modes are intentionally outside this SPI.
190
191
 
192
+ Current source reserves the shared execution slot for `idle_session`/`immediate` operations after
193
+ descriptor validation, through native settlement and awaited output acceptance. Caller turns and
194
+ native continuations cannot overlap that reservation. Exact cached idempotent results are returned
195
+ before reserving another execution. Close stops admission and initiates native quiescence before
196
+ joining entered operation work; failed cleanup retains its stopped owner for retry. This lifecycle
197
+ correction and operation retry observations are included in `0.4.3`, using the existing
198
+ Agent Protocol V12 and operation SPI.
199
+
200
+ Discover the operation through `session.operations` and its offered descriptor, using an exact
201
+ application-configured ID. Pass the descriptor revision as `expectedRevision`, its exact fields,
202
+ a fresh invocation ID and an independent observation correlation; the catalog revision is a
203
+ different value. Confirmation is host-owned. A completed result proves command completion, while
204
+ an accepted completed `context_compaction` item proves an observed compaction effect. Do not infer
205
+ effects from English output text, manufacture token savings, or replay an uncertain operation.
206
+ The exported repository's `examples/operations.ts` demonstrates these cases against public APIs
207
+ and is compiled and executed in the isolated packed-consumer gate.
208
+
191
209
  Configuration selection, operation invocation, and collaboration mutation use the same execution
192
210
  receipt rule: stale or malformed input fails before provider delegation, while any failure after
193
211
  the candidate reports execution start is wrapped as `AgentProviderDelegatedOperationError` and
@@ -533,13 +551,14 @@ close before joining its reader and actual callback tasks; failure also starts c
533
551
  failures and cleanup failures remain observable. A callback that ignores cancellation
534
552
  remains owned until its actual promise settles.
535
553
 
536
- The coordinated SDK release is `0.4.1` and implements Agent Protocol V11.
554
+ The coordinated SDK release is `0.4.3` and implements Agent Protocol V12.
555
+ The earlier `0.4.1` release retains V11.
537
556
  The earlier `0.4.0` release retains V10.
538
557
  The earlier public `0.3.0` and `0.3.1` releases implemented V9 and remain historical.
539
558
  Keep installed packages in the seven-package SDK release family on the coordinated version; the
540
559
  orchestration and ACP packages remain optional. Every materialized instance requires `environment` and
541
560
  `accountQuota`; every session requires `environment`. The former standalone `integrations`
542
- observation port is removed. The public runtime implements Agent Protocol V11 with one current
561
+ observation port is removed. The public runtime implements Agent Protocol V12 with one current
543
562
  adapter/session surface. Package versions remain independent of private transport and product
544
563
  persistence versions.
545
564
 
@@ -1,5 +1,7 @@
1
1
  import type { AgentProviderKey, AgentRequestId } from "@agen-ai/agent-protocol";
2
2
  export declare function createAgentSessionAdmission<Execution = unknown>(providerKey: AgentProviderKey): {
3
+ trackOperation(): () => void;
4
+ joinOperations(): Promise<void>;
3
5
  reserveExecution(): object;
4
6
  installExecution(token: object, execution: Execution): void;
5
7
  releaseExecution(token: object): void;
@@ -6,7 +6,20 @@ function createAgentSessionAdmission(providerKey) {
6
6
  let requestIdBytes = 0;
7
7
  let slot;
8
8
  let stopped = false;
9
+ const operations = /* @__PURE__ */ new Set();
9
10
  return {
11
+ trackOperation() {
12
+ if (stopped) throwAgentProviderContractError(providerKey, "session_unusable", "Session admission is stopped.");
13
+ const completion = Promise.withResolvers();
14
+ operations.add(completion.promise);
15
+ return () => {
16
+ operations.delete(completion.promise);
17
+ completion.resolve();
18
+ };
19
+ },
20
+ async joinOperations() {
21
+ await Promise.all(operations);
22
+ },
10
23
  reserveExecution() {
11
24
  if (stopped) throwAgentProviderContractError(providerKey, "session_unusable", "Session admission is stopped.");
12
25
  if (slot) throwAgentProviderContractError(providerKey, "concurrent_turn", "Provider session already owns an execution.");
@@ -49,6 +49,8 @@ interface ActiveAgentTurn {
49
49
  }
50
50
  export declare function createAgentSessionValidationOwner(capabilities: AgentCapabilities, sessionId: AgentSessionId): {
51
51
  sessionAdmission: {
52
+ trackOperation(): () => void;
53
+ joinOperations(): Promise<void>;
52
54
  reserveExecution(): object;
53
55
  installExecution(token: object, execution: ActiveAgentTurn): void;
54
56
  releaseExecution(token: object): void;
@@ -499,6 +499,7 @@ const SESSION_OPERATION_OBSERVATION_EVENT_TYPES = /* @__PURE__ */ new Set([
499
499
  "resource.updated",
500
500
  "runtime.warning",
501
501
  "runtime.error",
502
+ "runtime.retrying",
502
503
  "provider.diagnostic"
503
504
  ]);
504
505
  function observeSessionOperationOutput(input) {
@@ -1048,142 +1049,154 @@ function validateAgentProviderSession(input) {
1048
1049
  invokeOperation: async (operationInput) => {
1049
1050
  requireUsable();
1050
1051
  throwIfAgentOperationAborted(operationInput.signal);
1051
- let invocation;
1052
- let observationTurnId;
1052
+ const settle = validationOwner.sessionAdmission.trackOperation();
1053
+ const signal = operationInput.signal === void 0 ? sessionLifetime.signal : AbortSignal.any([operationInput.signal, sessionLifetime.signal]);
1054
+ operationInput = { ...operationInput, signal };
1053
1055
  try {
1054
- invocation = parseAgentOperationInvocation(operationInput.invocation);
1055
- observationTurnId = parseAgentTurnId(
1056
- operationInput.observationTurnId
1057
- );
1058
- } catch {
1059
- return throwAgentProviderContractError(
1060
- providerKey,
1061
- "input_operation_mismatch",
1062
- "Operation invocation is invalid."
1063
- );
1064
- }
1065
- const catalog = validateAgentOperationCatalogForCapabilities(
1066
- capabilities,
1067
- await declaredOperations.listOperations(
1068
- operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1069
- )
1070
- );
1071
- const descriptor = catalog.operations.find(
1072
- (operation) => operation.operationId === invocation.operationId
1073
- );
1074
- if (descriptor === void 0) {
1075
- return throwAgentProviderContractError(
1076
- providerKey,
1077
- "input_operation_mismatch",
1078
- "Operation invocation does not identify an offered operation."
1079
- );
1080
- }
1081
- try {
1082
- invocation = parseAgentOperationInvocationFor(
1083
- descriptor,
1084
- invocation
1056
+ let invocation;
1057
+ let observationTurnId;
1058
+ try {
1059
+ invocation = parseAgentOperationInvocation(operationInput.invocation);
1060
+ observationTurnId = parseAgentTurnId(
1061
+ operationInput.observationTurnId
1062
+ );
1063
+ } catch {
1064
+ return throwAgentProviderContractError(
1065
+ providerKey,
1066
+ "input_operation_mismatch",
1067
+ "Operation invocation is invalid."
1068
+ );
1069
+ }
1070
+ const catalog = validateAgentOperationCatalogForCapabilities(
1071
+ capabilities,
1072
+ await declaredOperations.listOperations(
1073
+ operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1074
+ )
1085
1075
  );
1086
- } catch {
1087
- return throwAgentProviderContractError(
1088
- providerKey,
1089
- "input_operation_mismatch",
1090
- "Operation invocation is stale or outside the offered descriptor."
1076
+ const descriptor = catalog.operations.find(
1077
+ (operation) => operation.operationId === invocation.operationId
1091
1078
  );
1092
- }
1093
- const existing = interactionState.operationInvocations.get(
1094
- invocation.invocationId
1095
- );
1096
- if (existing !== void 0) {
1097
- if (JSON.stringify(existing.invocation) !== JSON.stringify(invocation)) {
1079
+ if (descriptor === void 0) {
1098
1080
  return throwAgentProviderContractError(
1099
1081
  providerKey,
1100
1082
  "input_operation_mismatch",
1101
- "Operation invocation ID was reused with conflicting input."
1083
+ "Operation invocation does not identify an offered operation."
1102
1084
  );
1103
1085
  }
1104
- if (descriptor.idempotency === "required" && existing.result !== void 0) {
1105
- return existing.result;
1086
+ try {
1087
+ invocation = parseAgentOperationInvocationFor(
1088
+ descriptor,
1089
+ invocation
1090
+ );
1091
+ } catch {
1092
+ return throwAgentProviderContractError(
1093
+ providerKey,
1094
+ "input_operation_mismatch",
1095
+ "Operation invocation is stale or outside the offered descriptor."
1096
+ );
1106
1097
  }
1107
- return throwAgentProviderContractError(
1108
- providerKey,
1109
- "input_operation_mismatch",
1110
- "Operation invocation ID is already active or is not replayable."
1098
+ const existing = interactionState.operationInvocations.get(
1099
+ invocation.invocationId
1111
1100
  );
1112
- }
1113
- const trackedInvocation = {
1114
- invocation
1115
- };
1116
- const observationState = {
1117
- fileChangeMode: capabilities.output.fileChanges,
1118
- observedItems: /* @__PURE__ */ new Map(),
1119
- proposedPlans: /* @__PURE__ */ new Map(),
1120
- generatedResourceIds: /* @__PURE__ */ new Set(),
1121
- started: true,
1122
- terminal: false,
1123
- finalDiffObserved: false
1124
- };
1125
- interactionState.operationInvocations.set(
1126
- invocation.invocationId,
1127
- trackedInvocation
1128
- );
1129
- try {
1130
- const result = await delegateProviderMutation({
1131
- operation: "invoke_operation",
1132
- observer: operationInput.onProviderExecutionStarted,
1133
- invoke: (onProviderExecutionStarted, onOutput) => declaredOperations.invokeOperation({
1134
- invocation,
1135
- observationTurnId,
1136
- onProviderExecutionStarted,
1137
- onOutput,
1138
- ...operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1139
- }),
1140
- observeOutput: async (candidate) => {
1141
- const output = validateAgentProviderOutputForContext(
1142
- candidate,
1143
- {
1144
- ...outputContext(observationTurnId),
1145
- operationInvocationId: invocation.invocationId
1146
- }
1147
- );
1148
- observeSessionOperationOutput({
1149
- capabilities,
1150
- providerKey,
1151
- output,
1152
- state: observationState,
1153
- contextUsageState,
1154
- interactionState
1155
- });
1156
- await operationInput.onOutput?.(output);
1157
- },
1158
- validate: (candidate) => {
1159
- const result2 = validateAgentOperationResultTransition({
1101
+ if (existing !== void 0) {
1102
+ if (JSON.stringify(existing.invocation) !== JSON.stringify(invocation)) {
1103
+ return throwAgentProviderContractError(
1160
1104
  providerKey,
1161
- candidate: validateAgentOperationResultForInvocation(
1162
- providerKey,
1163
- descriptor,
1164
- invocation,
1165
- candidate
1166
- ),
1167
- previous: interactionState.operationResults.get(
1168
- invocation.invocationId
1169
- )
1170
- });
1171
- interactionState.operationResults.set(
1172
- result2.invocationId,
1173
- result2
1105
+ "input_operation_mismatch",
1106
+ "Operation invocation ID was reused with conflicting input."
1174
1107
  );
1175
- return result2;
1176
1108
  }
1177
- });
1178
- trackedInvocation.result = result;
1179
- return result;
1180
- } catch (error) {
1181
- if (!(error instanceof AgentProviderDelegatedOperationError)) {
1182
- interactionState.operationInvocations.delete(
1183
- invocation.invocationId
1109
+ if (descriptor.idempotency === "required" && existing.result !== void 0) {
1110
+ return existing.result;
1111
+ }
1112
+ return throwAgentProviderContractError(
1113
+ providerKey,
1114
+ "input_operation_mismatch",
1115
+ "Operation invocation ID is already active or is not replayable."
1184
1116
  );
1185
1117
  }
1186
- throw error;
1118
+ requireUsable();
1119
+ signal.throwIfAborted();
1120
+ const reservation = descriptor.timing === "idle_session" && descriptor.executionMode === "immediate" ? validationOwner.reserveExecution() : void 0;
1121
+ const trackedInvocation = {
1122
+ invocation
1123
+ };
1124
+ const observationState = {
1125
+ fileChangeMode: capabilities.output.fileChanges,
1126
+ observedItems: /* @__PURE__ */ new Map(),
1127
+ proposedPlans: /* @__PURE__ */ new Map(),
1128
+ generatedResourceIds: /* @__PURE__ */ new Set(),
1129
+ started: true,
1130
+ terminal: false,
1131
+ finalDiffObserved: false
1132
+ };
1133
+ interactionState.operationInvocations.set(
1134
+ invocation.invocationId,
1135
+ trackedInvocation
1136
+ );
1137
+ try {
1138
+ const result = await delegateProviderMutation({
1139
+ operation: "invoke_operation",
1140
+ observer: operationInput.onProviderExecutionStarted,
1141
+ invoke: (onProviderExecutionStarted, onOutput) => declaredOperations.invokeOperation({
1142
+ invocation,
1143
+ observationTurnId,
1144
+ onProviderExecutionStarted,
1145
+ onOutput,
1146
+ ...operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1147
+ }),
1148
+ observeOutput: async (candidate) => {
1149
+ const output = validateAgentProviderOutputForContext(
1150
+ candidate,
1151
+ {
1152
+ ...outputContext(observationTurnId),
1153
+ operationInvocationId: invocation.invocationId
1154
+ }
1155
+ );
1156
+ observeSessionOperationOutput({
1157
+ capabilities,
1158
+ providerKey,
1159
+ output,
1160
+ state: observationState,
1161
+ contextUsageState,
1162
+ interactionState
1163
+ });
1164
+ await operationInput.onOutput?.(output);
1165
+ },
1166
+ validate: (candidate) => {
1167
+ const result2 = validateAgentOperationResultTransition({
1168
+ providerKey,
1169
+ candidate: validateAgentOperationResultForInvocation(
1170
+ providerKey,
1171
+ descriptor,
1172
+ invocation,
1173
+ candidate
1174
+ ),
1175
+ previous: interactionState.operationResults.get(
1176
+ invocation.invocationId
1177
+ )
1178
+ });
1179
+ interactionState.operationResults.set(
1180
+ result2.invocationId,
1181
+ result2
1182
+ );
1183
+ return result2;
1184
+ }
1185
+ });
1186
+ trackedInvocation.result = result;
1187
+ return result;
1188
+ } catch (error) {
1189
+ if (!(error instanceof AgentProviderDelegatedOperationError)) {
1190
+ interactionState.operationInvocations.delete(
1191
+ invocation.invocationId
1192
+ );
1193
+ }
1194
+ throw error;
1195
+ } finally {
1196
+ if (reservation !== void 0) validationOwner.releaseExecution(reservation);
1197
+ }
1198
+ } finally {
1199
+ settle();
1187
1200
  }
1188
1201
  }
1189
1202
  });
@@ -1450,6 +1463,7 @@ function validateAgentProviderSession(input) {
1450
1463
  }
1451
1464
  await input.sessionInteractionOwner?.join();
1452
1465
  await closingTurn?.owner.join();
1466
+ await validationOwner.sessionAdmission.joinOperations();
1453
1467
  await input.hostBindingLease?.release();
1454
1468
  validationOwner.completeClose();
1455
1469
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/agent-runtime",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "private": false,
5
5
  "description": "Provider-neutral driver, instance, adapter, session, output, and conformance contracts for coding-agent runtimes.",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@agen-ai/agent-protocol": "^0.4.2"
34
+ "@agen-ai/agent-protocol": "^0.4.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "tsup": "^8.5.0"