@nylorun/runtime 0.42.0-beta → 0.44.0-beta

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/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Runtime API",
5
- "version": "0.42.0-beta",
5
+ "version": "0.44.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -43,7 +43,9 @@
43
43
  "agent-model",
44
44
  "secret-routing",
45
45
  "remove-tools",
46
- "agent-triggers"
46
+ "agent-triggers",
47
+ "steer",
48
+ "queue-recovery"
47
49
  ]
48
50
  },
49
51
  "description": "For developers: agent manifests, Agents, sessions (with AG-UI and A2A) and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps)."
@@ -6336,6 +6338,13 @@
6336
6338
  "type": "integer",
6337
6339
  "exclusiveMinimum": 0
6338
6340
  },
6341
+ "delivery": {
6342
+ "type": "string",
6343
+ "enum": [
6344
+ "queue",
6345
+ "steer"
6346
+ ]
6347
+ },
6339
6348
  "cursor": {
6340
6349
  "type": [
6341
6350
  "string",
@@ -6788,7 +6797,8 @@
6788
6797
  "type": "string",
6789
6798
  "enum": [
6790
6799
  "queue",
6791
- "reject"
6800
+ "reject",
6801
+ "steer"
6792
6802
  ]
6793
6803
  },
6794
6804
  "content": {
@@ -7116,7 +7126,8 @@
7116
7126
  "type": "string",
7117
7127
  "enum": [
7118
7128
  "queue",
7119
- "reject"
7129
+ "reject",
7130
+ "steer"
7120
7131
  ]
7121
7132
  },
7122
7133
  "data": {
@@ -7443,7 +7454,8 @@
7443
7454
  "type": "string",
7444
7455
  "enum": [
7445
7456
  "queue",
7446
- "reject"
7457
+ "reject",
7458
+ "steer"
7447
7459
  ]
7448
7460
  },
7449
7461
  "parts": {
@@ -9330,6 +9342,12 @@
9330
9342
  {
9331
9343
  "$ref": "#/components/schemas/QueueTakenEvent"
9332
9344
  },
9345
+ {
9346
+ "$ref": "#/components/schemas/QueueFailedEvent"
9347
+ },
9348
+ {
9349
+ "$ref": "#/components/schemas/QueueCancelledEvent"
9350
+ },
9333
9351
  {
9334
9352
  "$ref": "#/components/schemas/WakeBookedEvent"
9335
9353
  },
@@ -9426,6 +9444,8 @@
9426
9444
  "session.model": "#/components/schemas/SessionModelEvent",
9427
9445
  "message.queued": "#/components/schemas/MessageQueuedEvent",
9428
9446
  "queue.taken": "#/components/schemas/QueueTakenEvent",
9447
+ "queue.failed": "#/components/schemas/QueueFailedEvent",
9448
+ "queue.cancelled": "#/components/schemas/QueueCancelledEvent",
9429
9449
  "wake.booked": "#/components/schemas/WakeBookedEvent",
9430
9450
  "wake.cancelled": "#/components/schemas/WakeCancelledEvent",
9431
9451
  "memory.updated": "#/components/schemas/MemoryUpdatedEvent",
@@ -9700,7 +9720,8 @@
9700
9720
  "type": "string",
9701
9721
  "enum": [
9702
9722
  "queue",
9703
- "reject"
9723
+ "reject",
9724
+ "steer"
9704
9725
  ]
9705
9726
  },
9706
9727
  "queuedMessageId": {
@@ -11856,6 +11877,13 @@
11856
11877
  },
11857
11878
  "requestId": {
11858
11879
  "type": "string"
11880
+ },
11881
+ "delivery": {
11882
+ "type": "string",
11883
+ "enum": [
11884
+ "queue",
11885
+ "steer"
11886
+ ]
11859
11887
  }
11860
11888
  },
11861
11889
  "required": [
@@ -12057,6 +12085,10 @@
12057
12085
  "type": "string"
12058
12086
  },
12059
12087
  "minItems": 1
12088
+ },
12089
+ "step": {
12090
+ "type": "integer",
12091
+ "exclusiveMinimum": 0
12060
12092
  }
12061
12093
  },
12062
12094
  "required": [
@@ -12087,6 +12119,421 @@
12087
12119
  "payload"
12088
12120
  ]
12089
12121
  },
12122
+ "QueueFailedEvent": {
12123
+ "type": "object",
12124
+ "properties": {
12125
+ "schema": {
12126
+ "type": "string",
12127
+ "enum": [
12128
+ "nylorun.event/2"
12129
+ ]
12130
+ },
12131
+ "eventId": {
12132
+ "type": "string"
12133
+ },
12134
+ "tenantId": {
12135
+ "type": "string",
12136
+ "minLength": 1
12137
+ },
12138
+ "sessionId": {
12139
+ "type": "string"
12140
+ },
12141
+ "runId": {
12142
+ "type": [
12143
+ "string",
12144
+ "null"
12145
+ ]
12146
+ },
12147
+ "turnId": {
12148
+ "type": [
12149
+ "string",
12150
+ "null"
12151
+ ]
12152
+ },
12153
+ "incarnation": {
12154
+ "type": "integer",
12155
+ "minimum": 0
12156
+ },
12157
+ "epoch": {
12158
+ "type": "integer",
12159
+ "minimum": 0
12160
+ },
12161
+ "seq": {
12162
+ "type": "integer",
12163
+ "minimum": 0
12164
+ },
12165
+ "cursor": {
12166
+ "type": "string"
12167
+ },
12168
+ "time": {
12169
+ "type": "string"
12170
+ },
12171
+ "schemaVersion": {
12172
+ "type": "integer",
12173
+ "exclusiveMinimum": 0
12174
+ },
12175
+ "source": {
12176
+ "type": "object",
12177
+ "properties": {
12178
+ "kind": {
12179
+ "type": "string",
12180
+ "enum": [
12181
+ "client",
12182
+ "core",
12183
+ "model-gate",
12184
+ "tool-gate",
12185
+ "egress-gate",
12186
+ "sandboxes",
12187
+ "edge",
12188
+ "init",
12189
+ "controller",
12190
+ "loop",
12191
+ "supervisor",
12192
+ "harness",
12193
+ "evidence",
12194
+ "operator",
12195
+ "human",
12196
+ "api"
12197
+ ]
12198
+ },
12199
+ "id": {
12200
+ "type": "string"
12201
+ }
12202
+ },
12203
+ "required": [
12204
+ "kind",
12205
+ "id"
12206
+ ]
12207
+ },
12208
+ "evidence": {
12209
+ "type": "string",
12210
+ "enum": [
12211
+ "observed",
12212
+ "measured",
12213
+ "attested",
12214
+ "claimed",
12215
+ "asserted"
12216
+ ]
12217
+ },
12218
+ "principal": {
12219
+ "type": "object",
12220
+ "properties": {
12221
+ "kind": {
12222
+ "type": "string",
12223
+ "enum": [
12224
+ "application",
12225
+ "subject",
12226
+ "flow-host",
12227
+ "core",
12228
+ "trigger"
12229
+ ]
12230
+ },
12231
+ "id": {
12232
+ "type": "string"
12233
+ }
12234
+ },
12235
+ "required": [
12236
+ "kind",
12237
+ "id"
12238
+ ],
12239
+ "additionalProperties": false
12240
+ },
12241
+ "trace": {
12242
+ "type": "object",
12243
+ "properties": {
12244
+ "traceId": {
12245
+ "type": "string"
12246
+ },
12247
+ "spanId": {
12248
+ "type": "string"
12249
+ },
12250
+ "parentSpanId": {
12251
+ "type": "string"
12252
+ }
12253
+ },
12254
+ "required": [
12255
+ "traceId",
12256
+ "spanId"
12257
+ ]
12258
+ },
12259
+ "visibility": {
12260
+ "type": "string",
12261
+ "enum": [
12262
+ "public",
12263
+ "internal",
12264
+ "restricted"
12265
+ ]
12266
+ },
12267
+ "retention": {
12268
+ "type": "string",
12269
+ "enum": [
12270
+ "full",
12271
+ "metadata"
12272
+ ]
12273
+ },
12274
+ "type": {
12275
+ "type": "string",
12276
+ "enum": [
12277
+ "queue.failed"
12278
+ ]
12279
+ },
12280
+ "payload": {
12281
+ "type": "object",
12282
+ "properties": {
12283
+ "queuedMessageIds": {
12284
+ "type": "array",
12285
+ "items": {
12286
+ "type": "string"
12287
+ },
12288
+ "minItems": 1
12289
+ },
12290
+ "error": {
12291
+ "type": "object",
12292
+ "properties": {
12293
+ "code": {
12294
+ "type": "string"
12295
+ },
12296
+ "message": {
12297
+ "type": "string"
12298
+ },
12299
+ "reason": {
12300
+ "type": "string"
12301
+ }
12302
+ },
12303
+ "required": [
12304
+ "code",
12305
+ "message"
12306
+ ],
12307
+ "additionalProperties": {}
12308
+ }
12309
+ },
12310
+ "required": [
12311
+ "queuedMessageIds",
12312
+ "error"
12313
+ ],
12314
+ "additionalProperties": {}
12315
+ }
12316
+ },
12317
+ "required": [
12318
+ "schema",
12319
+ "eventId",
12320
+ "tenantId",
12321
+ "sessionId",
12322
+ "runId",
12323
+ "turnId",
12324
+ "incarnation",
12325
+ "epoch",
12326
+ "seq",
12327
+ "cursor",
12328
+ "time",
12329
+ "schemaVersion",
12330
+ "source",
12331
+ "evidence",
12332
+ "visibility",
12333
+ "retention",
12334
+ "type",
12335
+ "payload"
12336
+ ]
12337
+ },
12338
+ "QueueCancelledEvent": {
12339
+ "type": "object",
12340
+ "properties": {
12341
+ "schema": {
12342
+ "type": "string",
12343
+ "enum": [
12344
+ "nylorun.event/2"
12345
+ ]
12346
+ },
12347
+ "eventId": {
12348
+ "type": "string"
12349
+ },
12350
+ "tenantId": {
12351
+ "type": "string",
12352
+ "minLength": 1
12353
+ },
12354
+ "sessionId": {
12355
+ "type": "string"
12356
+ },
12357
+ "runId": {
12358
+ "type": [
12359
+ "string",
12360
+ "null"
12361
+ ]
12362
+ },
12363
+ "turnId": {
12364
+ "type": [
12365
+ "string",
12366
+ "null"
12367
+ ]
12368
+ },
12369
+ "incarnation": {
12370
+ "type": "integer",
12371
+ "minimum": 0
12372
+ },
12373
+ "epoch": {
12374
+ "type": "integer",
12375
+ "minimum": 0
12376
+ },
12377
+ "seq": {
12378
+ "type": "integer",
12379
+ "minimum": 0
12380
+ },
12381
+ "cursor": {
12382
+ "type": "string"
12383
+ },
12384
+ "time": {
12385
+ "type": "string"
12386
+ },
12387
+ "schemaVersion": {
12388
+ "type": "integer",
12389
+ "exclusiveMinimum": 0
12390
+ },
12391
+ "source": {
12392
+ "type": "object",
12393
+ "properties": {
12394
+ "kind": {
12395
+ "type": "string",
12396
+ "enum": [
12397
+ "client",
12398
+ "core",
12399
+ "model-gate",
12400
+ "tool-gate",
12401
+ "egress-gate",
12402
+ "sandboxes",
12403
+ "edge",
12404
+ "init",
12405
+ "controller",
12406
+ "loop",
12407
+ "supervisor",
12408
+ "harness",
12409
+ "evidence",
12410
+ "operator",
12411
+ "human",
12412
+ "api"
12413
+ ]
12414
+ },
12415
+ "id": {
12416
+ "type": "string"
12417
+ }
12418
+ },
12419
+ "required": [
12420
+ "kind",
12421
+ "id"
12422
+ ]
12423
+ },
12424
+ "evidence": {
12425
+ "type": "string",
12426
+ "enum": [
12427
+ "observed",
12428
+ "measured",
12429
+ "attested",
12430
+ "claimed",
12431
+ "asserted"
12432
+ ]
12433
+ },
12434
+ "principal": {
12435
+ "type": "object",
12436
+ "properties": {
12437
+ "kind": {
12438
+ "type": "string",
12439
+ "enum": [
12440
+ "application",
12441
+ "subject",
12442
+ "flow-host",
12443
+ "core",
12444
+ "trigger"
12445
+ ]
12446
+ },
12447
+ "id": {
12448
+ "type": "string"
12449
+ }
12450
+ },
12451
+ "required": [
12452
+ "kind",
12453
+ "id"
12454
+ ],
12455
+ "additionalProperties": false
12456
+ },
12457
+ "trace": {
12458
+ "type": "object",
12459
+ "properties": {
12460
+ "traceId": {
12461
+ "type": "string"
12462
+ },
12463
+ "spanId": {
12464
+ "type": "string"
12465
+ },
12466
+ "parentSpanId": {
12467
+ "type": "string"
12468
+ }
12469
+ },
12470
+ "required": [
12471
+ "traceId",
12472
+ "spanId"
12473
+ ]
12474
+ },
12475
+ "visibility": {
12476
+ "type": "string",
12477
+ "enum": [
12478
+ "public",
12479
+ "internal",
12480
+ "restricted"
12481
+ ]
12482
+ },
12483
+ "retention": {
12484
+ "type": "string",
12485
+ "enum": [
12486
+ "full",
12487
+ "metadata"
12488
+ ]
12489
+ },
12490
+ "type": {
12491
+ "type": "string",
12492
+ "enum": [
12493
+ "queue.cancelled"
12494
+ ]
12495
+ },
12496
+ "payload": {
12497
+ "type": "object",
12498
+ "properties": {
12499
+ "queuedMessageIds": {
12500
+ "type": "array",
12501
+ "items": {
12502
+ "type": "string"
12503
+ },
12504
+ "minItems": 1
12505
+ },
12506
+ "reason": {
12507
+ "type": "string"
12508
+ }
12509
+ },
12510
+ "required": [
12511
+ "queuedMessageIds"
12512
+ ],
12513
+ "additionalProperties": {}
12514
+ }
12515
+ },
12516
+ "required": [
12517
+ "schema",
12518
+ "eventId",
12519
+ "tenantId",
12520
+ "sessionId",
12521
+ "runId",
12522
+ "turnId",
12523
+ "incarnation",
12524
+ "epoch",
12525
+ "seq",
12526
+ "cursor",
12527
+ "time",
12528
+ "schemaVersion",
12529
+ "source",
12530
+ "evidence",
12531
+ "visibility",
12532
+ "retention",
12533
+ "type",
12534
+ "payload"
12535
+ ]
12536
+ },
12090
12537
  "WakeBookedEvent": {
12091
12538
  "type": "object",
12092
12539
  "properties": {
@@ -34432,6 +34879,12 @@
34432
34879
  {
34433
34880
  "$ref": "#/components/schemas/QueueTakenEvent"
34434
34881
  },
34882
+ {
34883
+ "$ref": "#/components/schemas/QueueFailedEvent"
34884
+ },
34885
+ {
34886
+ "$ref": "#/components/schemas/QueueCancelledEvent"
34887
+ },
34435
34888
  {
34436
34889
  "$ref": "#/components/schemas/WakeBookedEvent"
34437
34890
  },
@@ -36864,7 +37317,7 @@
36864
37317
  "A2A"
36865
37318
  ],
36866
37319
  "summary": "Call an agent over A2A",
36867
- "description": "A2A 1.0 JSON-RPC, for a subject, under the Nylorun profile v2 (Host feature `a2a-profile-v2`). `SendMessage` blocks up to the Runtime limit `a2aWaitMs` (5 minutes by default; then the task is answered still `WORKING`) unless `returnImmediately`; `GetTask` (with `historyLength`), `CancelTask` and `ListTasks`; and the four push notification config methods, plus `configuration.taskPushNotificationConfig` in `SendMessage`: each update is POSTed as one `StreamResponse`, signed with Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature` with the Runtime's `a2a.push.secret`) and `X-A2A-Notification-Token`, at least once, retried for up to 24 hours, to hosts the Runtime allows (`a2a.push.allowHosts` for private ones). A message to a busy context is queued and answered with the task that will take it (`TASK_STATE_SUBMITTED`); `message.metadata.delivery: \"reject\"` asks for `UnsupportedOperationError` (reason `CONTEXT_BUSY`) instead. `message.metadata.removeTools` (Host feature `remove-tools`) names the tools a new message's turn may not use, as a session message's `removeTools`. A message is text, one data part, or text with `raw` files (stored as artifacts of the context's session) and `url` files (fetched by the Runtime), each at most the Runtime limit `inlineFileBytes` (20 MiB by default). An approval, question or connection the task waits for (`AUTH_REQUIRED` or `INPUT_REQUIRED`) is shown under `interactions/v1` and answered on the same task with `{ interactionId, approved }` or `{ interactionId, value }` under that URI in the message's `metadata`; text never approves. Artifacts carry the turn's output and the files it wrote, as `url` parts at `/v1/a2a/files/…` with `files/v1`; the task's metadata carries `progress/v1`, `telemetry/v1` and `effects/v1`. Extension data is answered only for the extensions activated with `A2A-Extensions`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors. Not served: streaming, `GetExtendedAgentCard`.",
37320
+ "description": "A2A 1.0 JSON-RPC, for a subject, under the Nylorun profile v2 (Host feature `a2a-profile-v2`). `SendMessage` blocks up to the Runtime limit `a2aWaitMs` (5 minutes by default; then the task is answered still `WORKING`) unless `returnImmediately`; `GetTask` (with `historyLength`), `CancelTask` and `ListTasks`; and the four push notification config methods, plus `configuration.taskPushNotificationConfig` in `SendMessage`: each update is POSTed as one `StreamResponse`, signed with Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature` with the Runtime's `a2a.push.secret`) and `X-A2A-Notification-Token`, at least once, retried for up to 24 hours, to hosts the Runtime allows (`a2a.push.allowHosts` for private ones). A message to a busy context is queued and answered with the task that will take it (`TASK_STATE_SUBMITTED`); `message.metadata.delivery: \"reject\"` asks for `UnsupportedOperationError` (reason `CONTEXT_BUSY`) instead. A queued task never waits for good (Host feature `queue-recovery`): when its Agent refuses its turn's start (a disk copy, a lost pod), the start is tried again until the Runtime limit `queuedStartMs` (15 minutes by default), and then, or at once for a refusal no retry changes, the task is `TASK_STATE_FAILED` with a status message saying why; `CancelTask` on a queued task takes it out of the queue, a task running in the context or not, and answers `TASK_STATE_CANCELED`. Both are pushed. `message.metadata.removeTools` (Host feature `remove-tools`) names the tools a new message's turn may not use, as a session message's `removeTools`. A message is text, one data part, or text with `raw` files (stored as artifacts of the context's session) and `url` files (fetched by the Runtime), each at most the Runtime limit `inlineFileBytes` (20 MiB by default). An approval, question or connection the task waits for (`AUTH_REQUIRED` or `INPUT_REQUIRED`) is shown under `interactions/v1` and answered on the same task with `{ interactionId, approved }` or `{ interactionId, value }` under that URI in the message's `metadata`; text never approves. Artifacts carry the turn's output and the files it wrote, as `url` parts at `/v1/a2a/files/…` with `files/v1`; the task's metadata carries `progress/v1`, `telemetry/v1` and `effects/v1`. Extension data is answered only for the extensions activated with `A2A-Extensions`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors. Not served: streaming, `GetExtendedAgentCard`.",
36868
37321
  "externalDocs": {
36869
37322
  "description": "The A2A specification",
36870
37323
  "url": "https://a2a-protocol.org/latest/specification/"
@@ -14,7 +14,11 @@
14
14
  * is not the row's is applied again.
15
15
  * - **Idle** (`trigger: idle`): no turn on a session that names it, and none ended within its
16
16
  * `lifecycle.idle` (at most the Tenant's `limits.idle`) → `Suspended` (`agent.suspended`,
17
- * reason `idle`). The volume is kept; the next turn start asks for `Running` again.
17
+ * reason `idle`). The volume is kept; the next turn start asks for `Running` again. Every
18
+ * running, ready pod has its idle timer armed (at `lastActiveAt` + idle) whenever it is
19
+ * reconciled, whichever path woke it: a turn's end arms it too, but a wake with no turn end
20
+ * (a disk copy's `wakePod`, commands through the session's sandbox route) armed nothing and
21
+ * ran on for days. A timer that fires during a turn looks again one idle period later.
18
22
  * - **Lost**: an incarnation that has run (a pod joined) whose Sandbox is gone or whose volume
19
23
  * is missing is `lost` (`agent.lost`): its host epoch moves, its Sandbox is deleted, and
20
24
  * only a reset brings the Environment back. agent-sandbox recreates a deleted claim empty, so
@@ -118,6 +118,11 @@ export function decide(facts) {
118
118
  else
119
119
  arm.push({ timer: "idle", at: due });
120
120
  }
121
+ else if (facts.trigger === "idle" && desired === "running") {
122
+ // Busy: a turn's end arms the timer again, but look again anyway, so a turn that never ends
123
+ // cleanly doesn't keep the pod running for good.
124
+ arm.push({ timer: "idle", at: now + facts.config.idleMs });
125
+ }
121
126
  // An incarnation a pod has joined, whose Sandbox or volume is gone: lost.
122
127
  if (pod.podUid !== undefined && (!status.exists || status.deleting || status.volume === "missing")) {
123
128
  const reason = !status.exists
@@ -174,6 +179,9 @@ export function decide(facts) {
174
179
  const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID, facts.image) : undefined;
175
180
  if (digest !== undefined)
176
181
  patch.imageDigest = digest;
182
+ // Whatever woke it, a running pod has an idle timer (the idle trigger armed its own above).
183
+ if (facts.trigger !== "idle")
184
+ arm.push({ timer: "idle", at: Date.parse(pod.lastActiveAt ?? row.createdAt) + facts.config.idleMs });
177
185
  return done();
178
186
  }
179
187
  const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
@@ -16,9 +16,15 @@ export interface NetworkGateOptions {
16
16
  readonly pauseMs?: number;
17
17
  /** Consecutive failed connections that prove an address blocked. Default `harness.networkConfirmations` (3). */
18
18
  readonly confirmations?: number;
19
+ /**
20
+ * The pod starts on a disk an earlier pod of this sandbox used (a wake): check quickly, with
21
+ * `harness.wakeNetworkProbeMs` and `harness.wakeNetworkConfirmations`, until an address answers.
22
+ */
23
+ readonly wake?: boolean;
19
24
  readonly log?: (message: string, fields: Record<string, unknown>) => void;
20
25
  }
21
26
  /** Resolves once every address is blocked; rejects past the timeout. */
22
27
  export declare function awaitNetworkPolicy(options: NetworkGateOptions): Promise<{
23
28
  waitedMs: number;
29
+ check: "full" | "wake";
24
30
  }>;
@@ -6,6 +6,14 @@
6
6
  * `KUBERNETES_SERVICE_HOST:KUBERNETES_SERVICE_PORT`, which the kubelet always sets) until each
7
7
  * fails `harness.networkConfirmations` times in a row (Host tuning) (refused, unreachable or timed out). A connection that
8
8
  * succeeds starts the count again. Past `timeoutMs` the engine exits, and the pod restarts it.
9
+ *
10
+ * A wake (`wake`: the disk already holds the engine's `volume-id`, so a pod of this sandbox ran
11
+ * in this namespace before) starts the pod into a namespace whose policy has long been in force.
12
+ * The gate then takes `harness.wakeNetworkConfirmations` failed probes of
13
+ * `harness.wakeNetworkProbeMs` (1 of 150 ms) instead of three of a second: 152 ms instead of
14
+ * 3.0 s per wake (the A2 wake lab). It trusts a wake's quick check only while nothing answers:
15
+ * once a blocked address connects, the policy is not in force, and the gate goes back to the full
16
+ * check for every address.
9
17
  */
10
18
  import { connect } from "node:net";
11
19
  import { hostTuning } from "../../tuning.js";
@@ -47,14 +55,21 @@ export async function awaitNetworkPolicy(options) {
47
55
  const tuning = hostTuning().harness;
48
56
  const started = Date.now();
49
57
  const deadline = started + (options.timeoutMs ?? tuning.networkPolicyTimeoutMs);
50
- const needed = options.confirmations ?? tuning.networkConfirmations;
58
+ const full = {
59
+ needed: options.confirmations ?? tuning.networkConfirmations,
60
+ attemptMs: options.attemptMs ?? tuning.networkProbeMs,
61
+ };
62
+ const quick = { needed: tuning.wakeNetworkConfirmations, attemptMs: tuning.wakeNetworkProbeMs };
63
+ let check = options.wake ? "wake" : "full";
51
64
  for (const address of options.addresses) {
52
65
  let failures = 0;
53
66
  let opened = 0;
54
- while (failures < needed) {
67
+ while (failures < (check === "wake" ? quick : full).needed) {
55
68
  if (Date.now() > deadline)
56
69
  throw new Error(`The pod's NetworkPolicy is not in force: ${address.host}:${address.port} still answered ${opened} time(s)`);
57
- if (await opens(address, options.attemptMs ?? tuning.networkProbeMs)) {
70
+ if (await opens(address, (check === "wake" ? quick : full).attemptMs)) {
71
+ // An address the policy blocks answered: whatever the disk says, check in full.
72
+ check = "full";
58
73
  failures = 0;
59
74
  opened += 1;
60
75
  await new Promise((resolve) => setTimeout(resolve, options.pauseMs ?? tuning.networkPauseMs));
@@ -65,7 +80,8 @@ export async function awaitNetworkPolicy(options) {
65
80
  options.log?.("sandbox_network_policy_checked", {
66
81
  address: `${address.host}:${address.port}`,
67
82
  openedBefore: opened,
83
+ check,
68
84
  });
69
85
  }
70
- return { waitedMs: Date.now() - started };
86
+ return { waitedMs: Date.now() - started, check };
71
87
  }
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "nylorun"."session_queue" ADD COLUMN "delivery" text DEFAULT 'queue' NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "nylorun"."session_queue" ADD CONSTRAINT "session_queue_delivery_check" CHECK ("nylorun"."session_queue"."delivery" IN ('queue', 'steer'));
@@ -316,6 +316,13 @@
316
316
  "when": 1793548858191,
317
317
  "tag": "0044_agent_triggers_origin_backfill",
318
318
  "breakpoints": true
319
+ },
320
+ {
321
+ "idx": 45,
322
+ "version": "7",
323
+ "when": 1793954783377,
324
+ "tag": "0045_session_queue_delivery",
325
+ "breakpoints": true
319
326
  }
320
327
  ]
321
328
  }