@nylorun/runtime 0.51.0-beta → 0.51.2-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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.51.2-beta
4
+
5
+ ### Patch Changes
6
+
7
+ - b04a877: Pods on Agent Substrate (preview): a pod's engine tries its first join again while the edge can't be reached. A new actor can start before its egress policy reaches Substrate's egress, so the first join failed at the network ("fetch failed") and the engine exited; Substrate doesn't restart an actor's main process, so the actor stayed RUNNING with no engine and the start failed at the ready deadline (E13 on kind: 2 of 3 first starts). The first join is now tried again from 250 ms, doubling to 5 s, for up to 4 minutes (`sandbox_join_retry` in the engine's log); a refusal is still final at once.
8
+
9
+ ## 0.51.1-beta
10
+
11
+ ### Patch Changes
12
+
13
+ - 8451138: Pods on Agent Substrate (preview): a drain is recorded as soon as the moved pod's engine connects again. After suspend before drain, the restored engine redials with its host token (no join), and nothing asked the Runtime to reconcile the pod, so `agent.suspended` with reason `drain` waited for the next turn or idle timer (E13 on kind: not within two minutes). A pod engine's connection now asks for its sandbox's reconcile.
14
+
3
15
  ## 0.51.0-beta
4
16
 
5
17
  ### Minor Changes
@@ -27,7 +27,7 @@ import { createHostLogger } from "../host/logger.js";
27
27
  import { RUNTIME_VERSION } from "../version.js";
28
28
  import { localBackend, localExec } from "../adapters/sandbox/local.js";
29
29
  import { awaitNetworkPolicy } from "../sandbox/pods/network-gate.js";
30
- import { podHost } from "./pod.js";
30
+ import { firstJoin, podHost } from "./pod.js";
31
31
  import { prepareDisk } from "./setup.js";
32
32
  import { harnessRunTokens } from "./run-tokens.js";
33
33
  import { startHarnessService } from "./service.js";
@@ -59,8 +59,9 @@ export async function runHarness(stack, baseline) {
59
59
  caBundleFile: join(config.root, "egress-ca-bundle.pem"),
60
60
  ...(config.pod.egressCaFile ? { extraCaFile: config.pod.egressCaFile } : {}),
61
61
  });
62
- // Join, then make the disk ready (R2d step 2) before taking any turn.
63
- await pod.token();
62
+ // Join, then make the disk ready (R2d step 2) before taking any turn. The first join is tried
63
+ // again while the edge can't be reached yet (a new Agent Substrate actor's egress policy).
64
+ await firstJoin(pod, (message, fields) => logger.warn(message, fields));
64
65
  const host = pod;
65
66
  prepare = () => prepareDisk({
66
67
  host,
@@ -52,6 +52,23 @@ export interface PodHost {
52
52
  setupFile(index: number, path?: string): Promise<Response>;
53
53
  stop(): void;
54
54
  }
55
+ /**
56
+ * The engine's first join, tried again while the edge can't be reached. On Agent Substrate a new
57
+ * actor can start before its egress policy reaches the egress gateway, so its first request to
58
+ * the edge fails at the network ("fetch failed"), and an engine that then exited stayed down: an
59
+ * actor's main process is not restarted the way a pod's container is (E13, 2 of 3 first starts on
60
+ * a GitHub runner). A refusal (400, 401) is final at once; anything else is tried again with a
61
+ * pause from 250 ms doubling to 5 s, until `budgetMs` (default 4 min, inside the Runtime's 5-minute
62
+ * ready deadline) has passed.
63
+ */
64
+ export declare function firstJoin(host: Pick<PodHost, "token">, log: (message: string, fields: Record<string, unknown>) => void, options?: {
65
+ budgetMs?: number;
66
+ firstWaitMs?: number;
67
+ sleep?: (ms: number) => Promise<void>;
68
+ now?: () => number;
69
+ }): Promise<void>;
70
+ /** Whether `error` is the Runtime refusing a join or a renewal (final), not the network. */
71
+ export declare function isRefused(error: unknown): boolean;
55
72
  export declare function podHost(config: PodHostConfig, logger: {
56
73
  info(message: string, fields?: Record<string, unknown>): void;
57
74
  warn(message: string, fields?: Record<string, unknown>): void;
@@ -29,6 +29,38 @@ const TRUST_VARIABLES = [
29
29
  ];
30
30
  class Refused extends Error {
31
31
  }
32
+ /**
33
+ * The engine's first join, tried again while the edge can't be reached. On Agent Substrate a new
34
+ * actor can start before its egress policy reaches the egress gateway, so its first request to
35
+ * the edge fails at the network ("fetch failed"), and an engine that then exited stayed down: an
36
+ * actor's main process is not restarted the way a pod's container is (E13, 2 of 3 first starts on
37
+ * a GitHub runner). A refusal (400, 401) is final at once; anything else is tried again with a
38
+ * pause from 250 ms doubling to 5 s, until `budgetMs` (default 4 min, inside the Runtime's 5-minute
39
+ * ready deadline) has passed.
40
+ */
41
+ export async function firstJoin(host, log, options = {}) {
42
+ const now = options.now ?? Date.now;
43
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
44
+ const deadline = now() + (options.budgetMs ?? 240_000);
45
+ let wait = options.firstWaitMs ?? 250;
46
+ for (let attempt = 1;; attempt += 1) {
47
+ try {
48
+ await host.token();
49
+ return;
50
+ }
51
+ catch (error) {
52
+ if (error instanceof Refused || now() + wait > deadline)
53
+ throw error;
54
+ log("sandbox_join_retry", { attempt, waitMs: wait, error: error instanceof Error ? error.message : String(error) });
55
+ await sleep(wait);
56
+ wait = Math.min(wait * 2, 5_000);
57
+ }
58
+ }
59
+ }
60
+ /** Whether `error` is the Runtime refusing a join or a renewal (final), not the network. */
61
+ export function isRefused(error) {
62
+ return error instanceof Refused;
63
+ }
32
64
  export function podHost(config, logger, options = {}) {
33
65
  const call = options.fetch ?? fetch;
34
66
  const imageEngine = options.imageEngine ?? isBuiltinEngine(import.meta.url);
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Management API",
5
- "version": "0.51.0-beta",
5
+ "version": "0.51.2-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
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.51.0-beta",
5
+ "version": "0.51.2-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -392,7 +392,20 @@ export class TenantRuntime {
392
392
  return this.ctx.onSweep(hook);
393
393
  }
394
394
  attachHarness(channel, peer) {
395
- return this.ctx.harness.attach(channel, peer);
395
+ const detach = this.ctx.harness.attach(channel, peer);
396
+ // A pod's engine that (re)connects asks for its sandbox's reconcile: after a drain (Agent
397
+ // Substrate's eviction contract) the restored engine redials with its host token and no join,
398
+ // and this is what lets the Runtime see the move (`drainedAt`, `agent.suspended` reason
399
+ // `drain`) at once rather than at the next turn or idle timer.
400
+ const sandboxId = peer.host?.sandboxId;
401
+ if (sandboxId !== undefined)
402
+ void this.ctx.store
403
+ .tx((t) => t.signalSandbox(sandboxId, { kind: "reconcile" }))
404
+ .catch((error) => this.ctx.config.logger.warn("sandbox reconcile after connect failed", {
405
+ sandboxId,
406
+ error: error instanceof Error ? error.message : String(error),
407
+ }));
408
+ return detach;
396
409
  }
397
410
  harnessStatus() {
398
411
  return harnessStatusOf(this.ctx);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.51.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.51.2-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/server/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.51.0-beta";
3
+ export const RUNTIME_VERSION = "0.51.2-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.51.0-beta",
3
+ "version": "0.51.2-beta",
4
4
  "description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",