@nylorun/runtime 0.41.0-beta → 0.43.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/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.43.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - c649f33: A message can steer a running turn (D85). `delivery: "steer"` on a `message` command, behind the
8
+ optional Host feature `steer`: to a busy agent session it waits in the session queue marked
9
+ `steer`, and our engine takes it into the running turn before its next step, after the tool
10
+ results, through a new effect kind, `inputs`, which core answers in the transaction that journals
11
+ it (Harness API 5, `harness-api/v5.4.json`). Each taken message's `command.message` is written
12
+ under the running turn, then `queue.taken` with the `step`. The queued answer and
13
+ `message.queued` say which delivery applied; a workflow session queues. A steer the turn doesn't
14
+ take starts the next turn like any queued message, and D76's defaults never choose steer.
15
+
16
+ ### Patch Changes
17
+
18
+ - Pin core to the tested release.
19
+ - Pin harness to the tested release.
20
+ - Updated dependencies [c649f33]
21
+ - Updated dependencies
22
+ - @nylorun/core@0.37.0-beta
23
+ - @nylorun/harness@0.34.0-beta
24
+
25
+ ## 0.42.0-beta
26
+
27
+ ### Minor Changes
28
+
29
+ - 2afa44d: The Runtime's primitives as its own Agent Plugins (ADR-106, D84). An `agent-folder/3` folder lists them under `plugins:` (`nylorun-memory`, `nylorun-triggers`, `nylorun-plan`, `nylorun-interactions`, `nylorun-artifacts`, `nylorun-sandbox`, which needs a computer), with no versions, and turns one of their tools off with `mcpServers.nylorun.tools.<tool>.enabled: false`. The compiler reads them as the `agent-folder/2` declarations they stand for (`memory`, `loop`, `engine.sandbox`, `engine.artifacts`, `engine.triggers`, which v3 refuses) and records them in `harness.engine.plugins`, each with its tools and the hash of its files. The engine presents their tools as the reserved server `nylorun`: the model reads `nylorun__memory` and the declared plugins' words as one text, with their skills (`memory`, `triggers`) in the catalog, served from the bundle; every effect keeps its target and `toolName`, so the record, tool policy (`kind: "runtime"` rules) and dispatch are unchanged. New: `ask` (`nylorun-interactions`, `harness.loop.ask`), a question that pauses the turn as an A2A `question`; `removeTools` takes `nylorun__<tool>`; `triggers` answers carry `now`; under `nylorun-triggers`, an agent-made trigger's run starts with a `[trigger <id> · <title> · nobody is watching]` line, and a turn a trigger started may set a one-off follow-up (`in` or `at`, `session: "this"`); the sandbox's instructions name the hosts `runtime.json` allows. `nylorun migrate folder <dir> --to 3` writes the plugins that keep an agent's tools and features, and `nylorun check --explain` names the plugin behind each. The JSON Schemas are republished as `agent-manifest/v8.4`, `harness-manifest/v1.4` and `agent-folder/v1/agent-md.4`.
30
+
31
+ ### Patch Changes
32
+
33
+ - Pin core to the tested release.
34
+ - Pin harness to the tested release.
35
+ - Updated dependencies [2afa44d]
36
+ - Updated dependencies
37
+ - @nylorun/core@0.36.0-beta
38
+ - @nylorun/harness@0.33.0-beta
39
+
3
40
  ## 0.41.0-beta
4
41
 
5
42
  ### Minor Changes
@@ -2214,6 +2214,7 @@ export declare const SessionCommand: z.ZodUnion<readonly [z.ZodUnion<readonly [z
2214
2214
  delivery: z.ZodOptional<z.ZodEnum<{
2215
2215
  queue: "queue";
2216
2216
  reject: "reject";
2217
+ steer: "steer";
2217
2218
  }>>;
2218
2219
  content: z.ZodString;
2219
2220
  }, z.core.$strict>, z.ZodObject<{
@@ -2225,6 +2226,7 @@ export declare const SessionCommand: z.ZodUnion<readonly [z.ZodUnion<readonly [z
2225
2226
  delivery: z.ZodOptional<z.ZodEnum<{
2226
2227
  queue: "queue";
2227
2228
  reject: "reject";
2229
+ steer: "steer";
2228
2230
  }>>;
2229
2231
  data: z.ZodType<import("@nylorun/core/define").JsonValue, unknown, z.core.$ZodTypeInternals<import("@nylorun/core/define").JsonValue, unknown>>;
2230
2232
  }, z.core.$strict>, z.ZodObject<{
@@ -2236,6 +2238,7 @@ export declare const SessionCommand: z.ZodUnion<readonly [z.ZodUnion<readonly [z
2236
2238
  delivery: z.ZodOptional<z.ZodEnum<{
2237
2239
  queue: "queue";
2238
2240
  reject: "reject";
2241
+ steer: "steer";
2239
2242
  }>>;
2240
2243
  parts: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
2241
2244
  type: z.ZodLiteral<"text">;
@@ -2273,6 +2276,10 @@ export declare const QueuedResponse: z.ZodObject<{
2273
2276
  status: z.ZodLiteral<"queued">;
2274
2277
  queuedMessageId: z.ZodString;
2275
2278
  position: z.ZodNumber;
2279
+ delivery: z.ZodOptional<z.ZodEnum<{
2280
+ queue: "queue";
2281
+ steer: "steer";
2282
+ }>>;
2276
2283
  cursor: z.ZodNullable<z.ZodString>;
2277
2284
  requestId: z.ZodString;
2278
2285
  }, z.core.$strip>;
@@ -3309,6 +3316,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
3309
3316
  delivery: z.ZodOptional<z.ZodEnum<{
3310
3317
  queue: "queue";
3311
3318
  reject: "reject";
3319
+ steer: "steer";
3312
3320
  }>>;
3313
3321
  queuedMessageId: z.ZodOptional<z.ZodString>;
3314
3322
  cause: z.ZodOptional<z.ZodObject<{
@@ -4431,6 +4439,10 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
4431
4439
  queuedMessageId: z.ZodString;
4432
4440
  position: z.ZodNumber;
4433
4441
  requestId: z.ZodString;
4442
+ delivery: z.ZodOptional<z.ZodEnum<{
4443
+ queue: "queue";
4444
+ steer: "steer";
4445
+ }>>;
4434
4446
  }, z.core.$loose>;
4435
4447
  }, z.core.$strip> | z.ZodObject<{
4436
4448
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -4904,6 +4916,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
4904
4916
  payload: z.ZodObject<{
4905
4917
  turnId: z.ZodString;
4906
4918
  queuedMessageIds: z.ZodArray<z.ZodString>;
4919
+ step: z.ZodOptional<z.ZodNumber>;
4907
4920
  }, z.core.$loose>;
4908
4921
  }, z.core.$strip> | z.ZodObject<{
4909
4922
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -6388,6 +6401,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
6388
6401
  delivery: z.ZodOptional<z.ZodEnum<{
6389
6402
  queue: "queue";
6390
6403
  reject: "reject";
6404
+ steer: "steer";
6391
6405
  }>>;
6392
6406
  queuedMessageId: z.ZodOptional<z.ZodString>;
6393
6407
  cause: z.ZodOptional<z.ZodObject<{
@@ -7510,6 +7524,10 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
7510
7524
  queuedMessageId: z.ZodString;
7511
7525
  position: z.ZodNumber;
7512
7526
  requestId: z.ZodString;
7527
+ delivery: z.ZodOptional<z.ZodEnum<{
7528
+ queue: "queue";
7529
+ steer: "steer";
7530
+ }>>;
7513
7531
  }, z.core.$loose>;
7514
7532
  }, z.core.$strip> | z.ZodObject<{
7515
7533
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -7983,6 +8001,7 @@ export declare const SessionEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
7983
8001
  payload: z.ZodObject<{
7984
8002
  turnId: z.ZodString;
7985
8003
  queuedMessageIds: z.ZodArray<z.ZodString>;
8004
+ step: z.ZodOptional<z.ZodNumber>;
7986
8005
  }, z.core.$loose>;
7987
8006
  }, z.core.$strip> | z.ZodObject<{
7988
8007
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -9470,6 +9489,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
9470
9489
  delivery: z.ZodOptional<z.ZodEnum<{
9471
9490
  queue: "queue";
9472
9491
  reject: "reject";
9492
+ steer: "steer";
9473
9493
  }>>;
9474
9494
  queuedMessageId: z.ZodOptional<z.ZodString>;
9475
9495
  cause: z.ZodOptional<z.ZodObject<{
@@ -10592,6 +10612,10 @@ export declare const SessionItemsResponse: z.ZodObject<{
10592
10612
  queuedMessageId: z.ZodString;
10593
10613
  position: z.ZodNumber;
10594
10614
  requestId: z.ZodString;
10615
+ delivery: z.ZodOptional<z.ZodEnum<{
10616
+ queue: "queue";
10617
+ steer: "steer";
10618
+ }>>;
10595
10619
  }, z.core.$loose>;
10596
10620
  }, z.core.$strip> | z.ZodObject<{
10597
10621
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -11065,6 +11089,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
11065
11089
  payload: z.ZodObject<{
11066
11090
  turnId: z.ZodString;
11067
11091
  queuedMessageIds: z.ZodArray<z.ZodString>;
11092
+ step: z.ZodOptional<z.ZodNumber>;
11068
11093
  }, z.core.$loose>;
11069
11094
  }, z.core.$strip> | z.ZodObject<{
11070
11095
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -12549,6 +12574,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
12549
12574
  delivery: z.ZodOptional<z.ZodEnum<{
12550
12575
  queue: "queue";
12551
12576
  reject: "reject";
12577
+ steer: "steer";
12552
12578
  }>>;
12553
12579
  queuedMessageId: z.ZodOptional<z.ZodString>;
12554
12580
  cause: z.ZodOptional<z.ZodObject<{
@@ -13671,6 +13697,10 @@ export declare const SessionItemsResponse: z.ZodObject<{
13671
13697
  queuedMessageId: z.ZodString;
13672
13698
  position: z.ZodNumber;
13673
13699
  requestId: z.ZodString;
13700
+ delivery: z.ZodOptional<z.ZodEnum<{
13701
+ queue: "queue";
13702
+ steer: "steer";
13703
+ }>>;
13674
13704
  }, z.core.$loose>;
13675
13705
  }, z.core.$strip> | z.ZodObject<{
13676
13706
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -14144,6 +14174,7 @@ export declare const SessionItemsResponse: z.ZodObject<{
14144
14174
  payload: z.ZodObject<{
14145
14175
  turnId: z.ZodString;
14146
14176
  queuedMessageIds: z.ZodArray<z.ZodString>;
14177
+ step: z.ZodOptional<z.ZodNumber>;
14147
14178
  }, z.core.$loose>;
14148
14179
  }, z.core.$strip> | z.ZodObject<{
14149
14180
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -16546,6 +16577,7 @@ export declare const HistoryPage: z.ZodObject<{
16546
16577
  delivery: z.ZodOptional<z.ZodEnum<{
16547
16578
  queue: "queue";
16548
16579
  reject: "reject";
16580
+ steer: "steer";
16549
16581
  }>>;
16550
16582
  queuedMessageId: z.ZodOptional<z.ZodString>;
16551
16583
  cause: z.ZodOptional<z.ZodObject<{
@@ -17668,6 +17700,10 @@ export declare const HistoryPage: z.ZodObject<{
17668
17700
  queuedMessageId: z.ZodString;
17669
17701
  position: z.ZodNumber;
17670
17702
  requestId: z.ZodString;
17703
+ delivery: z.ZodOptional<z.ZodEnum<{
17704
+ queue: "queue";
17705
+ steer: "steer";
17706
+ }>>;
17671
17707
  }, z.core.$loose>;
17672
17708
  }, z.core.$strip> | z.ZodObject<{
17673
17709
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -18141,6 +18177,7 @@ export declare const HistoryPage: z.ZodObject<{
18141
18177
  payload: z.ZodObject<{
18142
18178
  turnId: z.ZodString;
18143
18179
  queuedMessageIds: z.ZodArray<z.ZodString>;
18180
+ step: z.ZodOptional<z.ZodNumber>;
18144
18181
  }, z.core.$loose>;
18145
18182
  }, z.core.$strip> | z.ZodObject<{
18146
18183
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -19625,6 +19662,7 @@ export declare const HistoryPage: z.ZodObject<{
19625
19662
  delivery: z.ZodOptional<z.ZodEnum<{
19626
19663
  queue: "queue";
19627
19664
  reject: "reject";
19665
+ steer: "steer";
19628
19666
  }>>;
19629
19667
  queuedMessageId: z.ZodOptional<z.ZodString>;
19630
19668
  cause: z.ZodOptional<z.ZodObject<{
@@ -20747,6 +20785,10 @@ export declare const HistoryPage: z.ZodObject<{
20747
20785
  queuedMessageId: z.ZodString;
20748
20786
  position: z.ZodNumber;
20749
20787
  requestId: z.ZodString;
20788
+ delivery: z.ZodOptional<z.ZodEnum<{
20789
+ queue: "queue";
20790
+ steer: "steer";
20791
+ }>>;
20750
20792
  }, z.core.$loose>;
20751
20793
  }, z.core.$strip> | z.ZodObject<{
20752
20794
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -21220,6 +21262,7 @@ export declare const HistoryPage: z.ZodObject<{
21220
21262
  payload: z.ZodObject<{
21221
21263
  turnId: z.ZodString;
21222
21264
  queuedMessageIds: z.ZodArray<z.ZodString>;
21265
+ step: z.ZodOptional<z.ZodNumber>;
21223
21266
  }, z.core.$loose>;
21224
21267
  }, z.core.$strip> | z.ZodObject<{
21225
21268
  schema: z.ZodLiteral<"nylorun.event/2">;
@@ -28,6 +28,7 @@ import { sessionSandboxSpec } from "../sandbox/share.js";
28
28
  import { readRuntimeLimits } from "../tenant/runtime-limits.js";
29
29
  import { checkEntry } from "../record/index.js";
30
30
  import { turnManifestOf } from "../tenant/session.js";
31
+ import { takeSteeredMessages } from "../tenant/commands.js";
31
32
  import { isEnabledIn, manifestFor, mcpToolIn } from "../mcp/snapshot.js";
32
33
  import { findServer } from "../mcp/pool.js";
33
34
  import { isHttpToolCall } from "../gates/http-tool.js";
@@ -127,6 +128,25 @@ export async function recordIntent(scope, effect, requestHash) {
127
128
  await t.put("effects", request.effectId, row);
128
129
  return { status: "execute", traceparent: row.traceparent };
129
130
  }
131
+ if (request.kind === "inputs") {
132
+ // The messages steered into the running turn (D85), taken before a step: journaled with
133
+ // what was taken, empty or not, so a replay takes the same messages at the same step.
134
+ const step = Number(request.input?.step);
135
+ const events = await takeSteeredMessages(t, s, s.id, Number.isInteger(step) && step > 0 ? step : 1);
136
+ if (events.length > 0)
137
+ await t.put("sessions", s.id, s);
138
+ const outcome = { value: { events } };
139
+ const at = new Date().toISOString();
140
+ await t.put("effects", request.effectId, {
141
+ request,
142
+ requestHash,
143
+ status: "completed",
144
+ outcome,
145
+ startedAt: at,
146
+ settledAt: at,
147
+ });
148
+ return { status: "completed", outcome };
149
+ }
130
150
  if (request.kind === "delegation") {
131
151
  // Lifecycle points of an agent used as a tool: journaled once, so replays never re-emit.
132
152
  const outcome = { value: null };
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Management API",
5
- "version": "0.41.0-beta",
5
+ "version": "0.43.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -43,7 +43,8 @@
43
43
  "agent-model",
44
44
  "secret-routing",
45
45
  "remove-tools",
46
- "agent-triggers"
46
+ "agent-triggers",
47
+ "steer"
47
48
  ]
48
49
  },
49
50
  "description": "For operators: the Tenant's models, vaults, signing keys, settings and application keys, at `/v1/tenant/*` on the Tenant's URL, through `@nylorun/admin`. Every operation takes a management key, acting as itself: never for a subject and never from a browser. Management keys are issued only on the Tenant's machine (`nylorun tenant keys put <id> --management`) or from `NYLORUN_MANAGEMENT_KEY_FILE`."
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.41.0-beta",
5
+ "version": "0.43.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 16,
8
8
  "max": 18,
@@ -43,7 +43,8 @@
43
43
  "agent-model",
44
44
  "secret-routing",
45
45
  "remove-tools",
46
- "agent-triggers"
46
+ "agent-triggers",
47
+ "steer"
47
48
  ]
48
49
  },
49
50
  "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 +6337,13 @@
6336
6337
  "type": "integer",
6337
6338
  "exclusiveMinimum": 0
6338
6339
  },
6340
+ "delivery": {
6341
+ "type": "string",
6342
+ "enum": [
6343
+ "queue",
6344
+ "steer"
6345
+ ]
6346
+ },
6339
6347
  "cursor": {
6340
6348
  "type": [
6341
6349
  "string",
@@ -6788,7 +6796,8 @@
6788
6796
  "type": "string",
6789
6797
  "enum": [
6790
6798
  "queue",
6791
- "reject"
6799
+ "reject",
6800
+ "steer"
6792
6801
  ]
6793
6802
  },
6794
6803
  "content": {
@@ -7116,7 +7125,8 @@
7116
7125
  "type": "string",
7117
7126
  "enum": [
7118
7127
  "queue",
7119
- "reject"
7128
+ "reject",
7129
+ "steer"
7120
7130
  ]
7121
7131
  },
7122
7132
  "data": {
@@ -7443,7 +7453,8 @@
7443
7453
  "type": "string",
7444
7454
  "enum": [
7445
7455
  "queue",
7446
- "reject"
7456
+ "reject",
7457
+ "steer"
7447
7458
  ]
7448
7459
  },
7449
7460
  "parts": {
@@ -8726,6 +8737,50 @@
8726
8737
  "additionalProperties": false
8727
8738
  },
8728
8739
  "description": "The engine text blocks the agent gets, with their hashes"
8740
+ },
8741
+ "plugins": {
8742
+ "type": "array",
8743
+ "items": {
8744
+ "type": "object",
8745
+ "properties": {
8746
+ "name": {
8747
+ "type": "string",
8748
+ "enum": [
8749
+ "nylorun-memory",
8750
+ "nylorun-triggers",
8751
+ "nylorun-plan",
8752
+ "nylorun-interactions",
8753
+ "nylorun-artifacts",
8754
+ "nylorun-sandbox"
8755
+ ]
8756
+ },
8757
+ "tools": {
8758
+ "type": "array",
8759
+ "items": {
8760
+ "type": "string",
8761
+ "minLength": 1
8762
+ }
8763
+ },
8764
+ "sha256": {
8765
+ "type": "string",
8766
+ "pattern": "^sha256:[0-9a-f]{64}$"
8767
+ },
8768
+ "hosts": {
8769
+ "type": "array",
8770
+ "items": {
8771
+ "type": "string",
8772
+ "minLength": 1
8773
+ }
8774
+ }
8775
+ },
8776
+ "required": [
8777
+ "name",
8778
+ "tools",
8779
+ "sha256"
8780
+ ],
8781
+ "additionalProperties": false
8782
+ },
8783
+ "description": "The Runtime's plugins the agent declares (agent-folder/3, ADR-106): each with the tools left on and the hash of its files"
8729
8784
  }
8730
8785
  },
8731
8786
  "additionalProperties": false
@@ -9656,7 +9711,8 @@
9656
9711
  "type": "string",
9657
9712
  "enum": [
9658
9713
  "queue",
9659
- "reject"
9714
+ "reject",
9715
+ "steer"
9660
9716
  ]
9661
9717
  },
9662
9718
  "queuedMessageId": {
@@ -11812,6 +11868,13 @@
11812
11868
  },
11813
11869
  "requestId": {
11814
11870
  "type": "string"
11871
+ },
11872
+ "delivery": {
11873
+ "type": "string",
11874
+ "enum": [
11875
+ "queue",
11876
+ "steer"
11877
+ ]
11815
11878
  }
11816
11879
  },
11817
11880
  "required": [
@@ -12013,6 +12076,10 @@
12013
12076
  "type": "string"
12014
12077
  },
12015
12078
  "minItems": 1
12079
+ },
12080
+ "step": {
12081
+ "type": "integer",
12082
+ "exclusiveMinimum": 0
12016
12083
  }
12017
12084
  },
12018
12085
  "required": [
@@ -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
  }
@@ -4484,7 +4484,8 @@ export declare const turns: import("drizzle-orm/pg-core").PgTableWithColumns<{
4484
4484
  }>;
4485
4485
  /**
4486
4486
  * Each session's queue (protocol 17, D76): messages that arrived while its turn ran or waited, in
4487
- * order (`seq`), until a turn takes them. A row holds the command as sent, the turn input its
4487
+ * order (`seq`), until a turn takes them: the next turn, or the running one before a step for a
4488
+ * steered message (D85). A row holds the command as sent, the turn input its
4488
4489
  * message gives the engine (file parts pinned when it was queued), and who sent it.
4489
4490
  */
4490
4491
  export declare const sessionQueue: import("drizzle-orm/pg-core").PgTableWithColumns<{
@@ -4679,6 +4680,23 @@ export declare const sessionQueue: import("drizzle-orm/pg-core").PgTableWithColu
4679
4680
  }, {}, {
4680
4681
  pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
4681
4682
  }>;
4683
+ delivery: import("drizzle-orm/pg-core").PgColumn<{
4684
+ name: "delivery";
4685
+ tableName: "session_queue";
4686
+ dataType: "string";
4687
+ columnType: "PgText";
4688
+ data: "queue" | "steer";
4689
+ driverParam: string;
4690
+ notNull: true;
4691
+ hasDefault: true;
4692
+ isPrimaryKey: false;
4693
+ isAutoincrement: false;
4694
+ hasRuntimeDefault: false;
4695
+ enumValues: ["queue", "steer"];
4696
+ baseColumn: never;
4697
+ identity: undefined;
4698
+ generated: undefined;
4699
+ }, {}, {}>;
4682
4700
  };
4683
4701
  dialect: 'pg';
4684
4702
  }>;
@@ -560,7 +560,8 @@ export const turns = nylorun.table("turns", {
560
560
  ]);
561
561
  /**
562
562
  * Each session's queue (protocol 17, D76): messages that arrived while its turn ran or waited, in
563
- * order (`seq`), until a turn takes them. A row holds the command as sent, the turn input its
563
+ * order (`seq`), until a turn takes them: the next turn, or the running one before a step for a
564
+ * steered message (D85). A row holds the command as sent, the turn input its
564
565
  * message gives the engine (file parts pinned when it was queued), and who sent it.
565
566
  */
566
567
  export const sessionQueue = nylorun.table("session_queue", {
@@ -580,7 +581,15 @@ export const sessionQueue = nylorun.table("session_queue", {
580
581
  /** What sent it when not a client (track T4): a trigger's run, or the session's own wake. */
581
582
  cause: jsonText(),
582
583
  createdAt: textC().notNull(),
583
- }, (t) => [uniqueIndex("session_queue_order").on(t.sessionId, t.seq)]);
584
+ /**
585
+ * `steer` (D85): the running turn may take it before a step (the `inputs` effect); `queue`:
586
+ * it waits for the turn to end. Either starts the next turn if the running one didn't take it.
587
+ */
588
+ delivery: text({ enum: ["queue", "steer"] }).notNull().default("queue"),
589
+ }, (t) => [
590
+ uniqueIndex("session_queue_order").on(t.sessionId, t.seq),
591
+ check("session_queue_delivery_check", sql `${t.delivery} IN ('queue', 'steer')`),
592
+ ]);
584
593
  /**
585
594
  * The Tool Gate's keyed MCP calls (F4.1 G3): one row per call, written by the gates service
586
595
  * before it calls the server and given the answer after. A re-send whose row has an answer
@@ -45,6 +45,14 @@ function reaches(row, sessionId) {
45
45
  return false;
46
46
  return row.createdSessionId === sessionId || row.definition?.session === sessionId;
47
47
  }
48
+ /** Whether the version session `s` runs declares the Runtime's `nylorun-triggers` plugin (ADR-106). */
49
+ async function declaresTriggersPlugin(t, s) {
50
+ if (!s.definition)
51
+ return false;
52
+ const body = (await t.agentManifest(s.definition.name, s.definition.version))?.body;
53
+ const plugins = body?.harness?.engine?.plugins;
54
+ return Array.isArray(plugins) && plugins.some((plugin) => plugin?.name === "nylorun-triggers");
55
+ }
48
56
  /** Whether the version session `s` runs declares `harness.engine.triggers`. */
49
57
  async function declaresTriggers(t, s) {
50
58
  if (!s.definition)
@@ -86,6 +94,7 @@ export async function serveTriggersTool(t, s, request) {
86
94
  if (input.command === "list") {
87
95
  const rows = (await t.agentTriggers(s.agentId)).filter((row) => reaches(row, s.id));
88
96
  return completed({
97
+ now: new Date().toISOString(),
89
98
  triggers: rows.map((row) => {
90
99
  const trigger = row.definition;
91
100
  return {
@@ -109,9 +118,17 @@ export async function serveTriggersTool(t, s, request) {
109
118
  await t.agentEvent(s.agentId, "agent.trigger_deleted", { triggerId: input.id, sessionId: s.id });
110
119
  return completed({ id: input.id, deleted: true });
111
120
  }
112
- // create: never from a turn a trigger or a booked wake started.
113
- if (s.turnCause !== undefined)
114
- return failed("tool.forbidden", "A run can't create triggers: this turn was started by a trigger. It may list and delete them.");
121
+ // create: never from a turn a trigger or a booked wake started. Under the `nylorun-triggers`
122
+ // plugin (ADR-106) such a turn may still set a one-off follow-up in its own session, as a woken
123
+ // turn books the next wake: never a schedule, never a session of its own.
124
+ if (s.turnCause !== undefined) {
125
+ const plugin = await declaresTriggersPlugin(t, s);
126
+ const followUp = plugin && input.schedule === undefined && input.session === "this";
127
+ if (!followUp)
128
+ return failed("tool.forbidden", plugin
129
+ ? "A run can't create triggers other than a one-off follow-up here (in or at, with session: this): this turn was started by a trigger. It may list and delete them."
130
+ : "A run can't create triggers: this turn was started by a trigger. It may list and delete them.");
131
+ }
115
132
  const now = new Date();
116
133
  const stamp = now.toISOString();
117
134
  const session = triggerSessionOf(s.id, input.id, input.session);
@@ -175,5 +192,5 @@ export async function serveTriggersTool(t, s, request) {
175
192
  ...(row.title === null ? {} : { title: row.title }),
176
193
  nextRunAt: row.nextFireAt,
177
194
  });
178
- return completed({ id: input.id, status: row.status, nextRunAt: row.nextFireAt, session }, [row]);
195
+ return completed({ id: input.id, status: row.status, nextRunAt: row.nextFireAt, session, now: stamp }, [row]);
179
196
  }
@@ -37,7 +37,10 @@ export interface MessageCause {
37
37
  triggerId: string;
38
38
  runId: string;
39
39
  }
40
- /** How a message was taken: it started a turn, or waits in the session's queue. */
40
+ /**
41
+ * How a message was taken: it started a turn, or waits in the session's queue, marked with the
42
+ * delivery that applied (D85): `steer` when the running turn may take it before a step.
43
+ */
41
44
  export type MessageAnswer = {
42
45
  status: "accepted";
43
46
  turnId: string;
@@ -47,15 +50,28 @@ export type MessageAnswer = {
47
50
  queuedMessageId: string;
48
51
  position: number;
49
52
  cursor: string;
53
+ delivery: "queue" | "steer";
50
54
  };
51
55
  /**
52
56
  * Takes a resolved message on session `s` (locked) in transaction `t`: it starts a turn when the
53
57
  * session takes messages and none wait, else it waits in the queue behind them (protocol 17), and
54
58
  * an idle session whose queue was left waiting (its Agent was unavailable when the last turn
55
59
  * ended) starts a turn with all of them, this one last. Past `sessionQueuedMessages` it is
56
- * `429 queue_full`. `waiting` is the queue's length, read under the session's lock.
60
+ * `429 queue_full`. `waiting` is the queue's length, read under the session's lock. `asked`
61
+ * `steer` (D85) marks a waiting message so the running turn may take it before a step; a
62
+ * workflow session can't, so it waits as `queue`.
63
+ */
64
+ export declare function acceptMessage(ctx: TenantContext, t: Tx, s: Session, id: string, message: TurnMessage, waiting?: number, asked?: "queue" | "steer"): Promise<MessageAnswer>;
65
+ /**
66
+ * Takes the messages steered into session `s`'s running turn (D85), before step `step`: the
67
+ * `inputs` effect, in the transaction that journals it (`harness-api/record.ts`). Only `steer`
68
+ * rows whose turn request (variant, `removeTools`) is the running turn's join it; the rest wait
69
+ * for the turn's end. Writes each one's `command.message` under the turn, then one `queue.taken`
70
+ * with the step, adds their file artifacts to the session's, and returns their inputs (each as its
71
+ * message gave it, which the engine reads as a user message) in order. A workflow session takes
72
+ * none.
57
73
  */
58
- export declare function acceptMessage(ctx: TenantContext, t: Tx, s: Session, id: string, message: TurnMessage, waiting?: number): Promise<MessageAnswer>;
74
+ export declare function takeSteeredMessages(t: Tx, s: Session, id: string, step: number): Promise<unknown[]>;
59
75
  /** Who checks a queued message's turn start: core, since the sender's grants were checked when it was queued. */
60
76
  export declare const QUEUE_SCOPE: AuthScope;
61
77
  /**
@@ -19,7 +19,10 @@
19
19
  * (`session_queue`, `message.queued`) unless it asks `delivery: "reject"`, which is
20
20
  * `409 session_busy`. A turn that ends takes every queued message in the same transaction
21
21
  * (`startQueuedTurn`): client messages and queued ones start a turn through one door,
22
- * `beginMessageTurn`, which writes each one's `command.message`.
22
+ * `beginMessageTurn`, which writes each one's `command.message`. A message that asks
23
+ * `delivery: "steer"` (D85) waits marked `steer`, and the running turn takes it before a step
24
+ * (`takeSteeredMessages`, the `inputs` effect); one it doesn't take starts the next turn like
25
+ * any other.
23
26
  */
24
27
  import { randomUUID } from "node:crypto";
25
28
  import { createDurableCheckpoint, createFlowCheckpoint, resumeFlowCheckpoint, } from "@nylorun/harness/run";
@@ -157,7 +160,7 @@ options = {}) {
157
160
  // Every turn start on a sandbox: it exists, the token's grants reach it, and no other
158
161
  // session's turn holds it. A queued message is checked when it is queued, too.
159
162
  await checkAgentTurn(t, s, scope, ctx);
160
- answer = await acceptMessage(ctx, t, s, id, await turnMessage(t, s, id, command, scope, options.trace), waiting);
163
+ answer = await acceptMessage(ctx, t, s, id, await turnMessage(t, s, id, command, scope, options.trace), waiting, delivery === "steer" ? "steer" : "queue");
161
164
  }
162
165
  else if (command.type === "approve" &&
163
166
  (held = await heldCall(t, s, command.interactionId)) !== undefined) {
@@ -299,9 +302,11 @@ export const takesMessages = (s) => TAKES_MESSAGES.includes(s.status);
299
302
  * session takes messages and none wait, else it waits in the queue behind them (protocol 17), and
300
303
  * an idle session whose queue was left waiting (its Agent was unavailable when the last turn
301
304
  * ended) starts a turn with all of them, this one last. Past `sessionQueuedMessages` it is
302
- * `429 queue_full`. `waiting` is the queue's length, read under the session's lock.
305
+ * `429 queue_full`. `waiting` is the queue's length, read under the session's lock. `asked`
306
+ * `steer` (D85) marks a waiting message so the running turn may take it before a step; a
307
+ * workflow session can't, so it waits as `queue`.
303
308
  */
304
- export async function acceptMessage(ctx, t, s, id, message, waiting) {
309
+ export async function acceptMessage(ctx, t, s, id, message, waiting, asked = "queue") {
305
310
  const queued = waiting ?? (await t.countQueuedMessages(id));
306
311
  const takes = takesMessages(s);
307
312
  if (takes && queued === 0) {
@@ -313,6 +318,7 @@ export async function acceptMessage(ctx, t, s, id, message, waiting) {
313
318
  if (queued >= sessionQueuedMessages)
314
319
  fail(429, `Session ${id} already holds ${queued} queued messages, the Runtime limit sessionQueuedMessages (PUT /v1/tenant/limits); they start a turn when the running one ends.`, { code: "queue_full" });
315
320
  const queuedMessageId = randomUUID();
321
+ const delivery = asked === "steer" && !isWorkflowManifest(s.manifest) ? "steer" : "queue";
316
322
  await t.enqueueMessage({
317
323
  id: queuedMessageId,
318
324
  sessionId: id,
@@ -323,13 +329,56 @@ export async function acceptMessage(ctx, t, s, id, message, waiting) {
323
329
  trace: message.trace ?? null,
324
330
  cause: message.cause ?? null,
325
331
  createdAt: new Date().toISOString(),
332
+ delivery,
326
333
  });
327
334
  const position = queued + 1;
328
- const event = await t.event(id, s.activeTurnId, "message.queued", { queuedMessageId, position, requestId: message.command.requestId }, message.writer);
335
+ const event = await t.event(id, s.activeTurnId, "message.queued", { queuedMessageId, position, requestId: message.command.requestId, delivery }, message.writer);
329
336
  const started = takes ? await startQueuedTurn(ctx, t, s, id) : undefined;
330
337
  if (started?.taken.includes(queuedMessageId))
331
338
  return { status: "accepted", turnId: started.turnId, cursor: started.cursor };
332
- return { status: "queued", queuedMessageId, position, cursor: event.cursor };
339
+ return { status: "queued", queuedMessageId, position, cursor: event.cursor, delivery };
340
+ }
341
+ /**
342
+ * Takes the messages steered into session `s`'s running turn (D85), before step `step`: the
343
+ * `inputs` effect, in the transaction that journals it (`harness-api/record.ts`). Only `steer`
344
+ * rows whose turn request (variant, `removeTools`) is the running turn's join it; the rest wait
345
+ * for the turn's end. Writes each one's `command.message` under the turn, then one `queue.taken`
346
+ * with the step, adds their file artifacts to the session's, and returns their inputs (each as its
347
+ * message gave it, which the engine reads as a user message) in order. A workflow session takes
348
+ * none.
349
+ */
350
+ export async function takeSteeredMessages(t, s, id, step) {
351
+ const turnId = s.activeTurnId;
352
+ if (turnId === null || isWorkflowManifest(s.manifest))
353
+ return [];
354
+ const running = s.turnRequest?.turnId === turnId ? s.turnRequest : undefined;
355
+ if (running?.manifest !== undefined)
356
+ return [];
357
+ const runningTools = canonical([...new Set(running?.removeTools ?? [])].sort());
358
+ const rows = (await t.queuedMessages(id)).filter((row) => {
359
+ if (row.delivery !== "steer")
360
+ return false;
361
+ const request = turnManifestRequestOf(row.command);
362
+ if (request?.manifest !== undefined)
363
+ return false;
364
+ return canonical([...(request?.removeTools ?? [])].sort()) === runningTools;
365
+ });
366
+ if (rows.length === 0)
367
+ return [];
368
+ const taken = rows.map((row) => row.id);
369
+ await t.deleteQueuedMessages(taken);
370
+ const named = rows.flatMap((row) => row.fileArtifacts);
371
+ if (named.length > 0)
372
+ s.fileArtifacts = [...new Set([...(s.fileArtifacts ?? []), ...named])];
373
+ for (const row of rows)
374
+ await t.event(id, turnId, "command.message", {
375
+ ...row.command,
376
+ queuedMessageId: row.id,
377
+ ...(row.trace ? { trace: row.trace } : {}),
378
+ ...(row.cause ? { cause: row.cause } : {}),
379
+ }, row.writer);
380
+ await t.event(id, turnId, "queue.taken", { turnId, queuedMessageIds: taken, step });
381
+ return rows.map((row) => row.input);
333
382
  }
334
383
  /**
335
384
  * Resolves a message for a turn, now or when it leaves the queue: file parts name artifacts the
@@ -31,6 +31,11 @@ export declare function nextFireAt(agentId: string, triggerId: string, trigger:
31
31
  export declare function triggerIssues(triggers: Readonly<Record<string, Trigger>> | undefined, limits: RuntimeLimits, now?: Date,
32
32
  /** How an issue names trigger `id`: its place in the manifest by default. */
33
33
  label?: (id: string) => string): string[];
34
+ /**
35
+ * The line an agent-made trigger's run starts with under the `nylorun-triggers` plugin (ADR-106):
36
+ * which trigger woke the turn, and that nobody is watching.
37
+ */
38
+ export declare function triggerFrame(triggerId: string, title: string | null | undefined): string;
34
39
  /** Where a row's definition comes from (D83): its origin, or `wake` for a booked wake written before origins. */
35
40
  export declare function originOf(row: Pick<AgentTriggerRow, "origin" | "kind">): AgentTriggerRow["origin"];
36
41
  /**
@@ -152,7 +152,20 @@ async function triggersOf(t, agent) {
152
152
  async function pinnedOf(t, agent) {
153
153
  const body = (await t.agentManifest(agent.manifestName, agent.manifestVersion))?.body;
154
154
  const harness = body?.harness;
155
- return { triggers: (body?.runtime?.triggers ?? {}), declared: harness?.engine?.triggers === true };
155
+ const plugins = harness?.engine?.plugins;
156
+ return {
157
+ triggers: (body?.runtime?.triggers ?? {}),
158
+ declared: harness?.engine?.triggers === true,
159
+ // The version declares the Runtime's `nylorun-triggers` plugin (ADR-106).
160
+ plugin: Array.isArray(plugins) && plugins.some((item) => item?.name === "nylorun-triggers"),
161
+ };
162
+ }
163
+ /**
164
+ * The line an agent-made trigger's run starts with under the `nylorun-triggers` plugin (ADR-106):
165
+ * which trigger woke the turn, and that nobody is watching.
166
+ */
167
+ export function triggerFrame(triggerId, title) {
168
+ return `[trigger ${triggerId}${title ? ` · ${title}` : ""} · nobody is watching]`;
156
169
  }
157
170
  /** Where a row's definition comes from (D83): its origin, or `wake` for a booked wake written before origins. */
158
171
  export function originOf(row) {
@@ -431,7 +444,9 @@ export async function deliverRun(ctx, t, agent, triggerId, trigger, row, cause)
431
444
  title: row.title ?? null,
432
445
  streamedAt: null,
433
446
  };
434
- const outcome = await runOutcome(ctx, t, agent, triggerId, trigger, row.kind, id, firedAt, cause.payload, row.createdSessionId ?? null);
447
+ // Under the `nylorun-triggers` plugin, an agent-made trigger's run is framed (ADR-106).
448
+ const framed = origin === "agent" && (await pinnedOf(t, agent)).plugin ? { ...trigger, prompt: `${triggerFrame(triggerId, row.title)}\n${trigger.prompt}` } : trigger;
449
+ const outcome = await runOutcome(ctx, t, agent, triggerId, framed, row.kind, id, firedAt, cause.payload, row.createdSessionId ?? null);
435
450
  Object.assign(run, outcome);
436
451
  await t.insertTriggerRun(run);
437
452
  await t.agentEvent(agent.id, "agent.run", {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.41.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.43.0-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.41.0-beta";
3
+ export const RUNTIME_VERSION = "0.43.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.41.0-beta",
3
+ "version": "0.43.0-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",
@@ -65,8 +65,8 @@
65
65
  "@hono/node-server": "^2.1.5",
66
66
  "@hono/zod-openapi": "^1.6.3",
67
67
  "@modelcontextprotocol/sdk": "^1.32.1",
68
- "@nylorun/core": "0.35.0-beta",
69
- "@nylorun/harness": "0.32.0-beta",
68
+ "@nylorun/core": "0.37.0-beta",
69
+ "@nylorun/harness": "0.34.0-beta",
70
70
  "@restatedev/restate-sdk": "1.17.3",
71
71
  "@restatedev/restate-sdk-clients": "1.17.3",
72
72
  "@s2-dev/streamstore": "0.28.0",