@nylorun/runtime 0.27.0-beta → 0.29.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +1 -1
  3. package/dist/api/a2a/card.d.ts +4 -3
  4. package/dist/api/a2a/card.js +3 -3
  5. package/dist/api/a2a/routes.js +18 -7
  6. package/dist/api/ag-ui/routes.js +4 -7
  7. package/dist/api/components.d.ts +5854 -2718
  8. package/dist/api/components.js +39 -30
  9. package/dist/api/http/app.js +9 -4
  10. package/dist/api/http/body.d.ts +2 -0
  11. package/dist/api/http/body.js +20 -0
  12. package/dist/api/http/define.d.ts +4 -0
  13. package/dist/api/http/define.js +20 -9
  14. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  15. package/dist/api/http/routes/agent-manifests.js +135 -0
  16. package/dist/api/http/routes/agents.d.ts +11 -0
  17. package/dist/api/http/routes/agents.js +418 -0
  18. package/dist/api/http/routes/artifacts.js +2 -2
  19. package/dist/api/http/routes/coverage.d.ts +4 -4
  20. package/dist/api/http/routes/coverage.js +12 -6
  21. package/dist/api/http/routes/files.js +1 -1
  22. package/dist/api/http/routes/mcp.js +2 -2
  23. package/dist/api/http/routes/me.d.ts +1 -1
  24. package/dist/api/http/routes/me.js +1 -2
  25. package/dist/api/http/routes/memories.d.ts +9 -0
  26. package/dist/api/http/routes/memories.js +152 -0
  27. package/dist/api/http/routes/reads.d.ts +1 -137
  28. package/dist/api/http/routes/reads.js +31 -12
  29. package/dist/api/http/routes/sessions.js +21 -50
  30. package/dist/api/http/routes/tenant.js +13 -10
  31. package/dist/api/http/routes/triggers.d.ts +10 -0
  32. package/dist/api/http/routes/triggers.js +183 -0
  33. package/dist/api/openapi.js +69 -47
  34. package/dist/artifacts/disk.d.ts +10 -10
  35. package/dist/artifacts/disk.js +27 -27
  36. package/dist/artifacts/quota.d.ts +1 -1
  37. package/dist/artifacts/quota.js +2 -2
  38. package/dist/artifacts/service.js +2 -2
  39. package/dist/core/flow-host.d.ts +0 -1
  40. package/dist/gates/egress-credentials.js +1 -1
  41. package/dist/gates/egress.d.ts +5 -5
  42. package/dist/gates/egress.js +6 -6
  43. package/dist/gates/http-tool.js +2 -2
  44. package/dist/gates/meter.js +22 -5
  45. package/dist/gates/policy.d.ts +8 -3
  46. package/dist/gates/policy.js +7 -2
  47. package/dist/gates/tenant-vaults.d.ts +1 -1
  48. package/dist/harness/pod.d.ts +2 -0
  49. package/dist/harness/setup.js +17 -6
  50. package/dist/harness-api/record.js +11 -0
  51. package/dist/harness-api/server.js +4 -0
  52. package/dist/harness-api/start.d.ts +11 -7
  53. package/dist/harness-api/start.js +49 -5
  54. package/dist/harness-api/workspace.js +2 -2
  55. package/dist/host/config-for.d.ts +2 -0
  56. package/dist/host/config-for.js +1 -0
  57. package/dist/host/http.d.ts +2 -2
  58. package/dist/host/http.js +5 -2
  59. package/dist/host/main.js +1 -0
  60. package/dist/keys/keys.d.ts +15 -4
  61. package/dist/keys/keys.js +16 -0
  62. package/dist/keys/webhooks.d.ts +25 -0
  63. package/dist/keys/webhooks.js +62 -0
  64. package/dist/management-openapi.json +313 -133
  65. package/dist/mcp/connect.d.ts +2 -2
  66. package/dist/mcp/connect.js +3 -3
  67. package/dist/mcp/preview.js +7 -7
  68. package/dist/memory/service.d.ts +97 -0
  69. package/dist/memory/service.js +335 -0
  70. package/dist/memory/tool.d.ts +36 -0
  71. package/dist/memory/tool.js +84 -0
  72. package/dist/model/pi-model.js +7 -0
  73. package/dist/openapi.json +13120 -9034
  74. package/dist/reads/types.d.ts +3 -3
  75. package/dist/record/agent.d.ts +27 -0
  76. package/dist/record/agent.js +46 -0
  77. package/dist/record/index.d.ts +1 -1
  78. package/dist/record/index.js +1 -1
  79. package/dist/sandbox/join.js +5 -5
  80. package/dist/sandbox/placement.d.ts +1 -1
  81. package/dist/sandbox/placement.js +3 -3
  82. package/dist/sandbox/pods/lifecycle.d.ts +6 -6
  83. package/dist/sandbox/pods/lifecycle.js +6 -6
  84. package/dist/sandbox/pods/reconcile.js +10 -2
  85. package/dist/sandbox/resolve.d.ts +3 -3
  86. package/dist/sandbox/resolve.js +7 -7
  87. package/dist/sandbox/setup.d.ts +3 -1
  88. package/dist/sandbox/setup.js +14 -4
  89. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  90. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  91. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  92. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  93. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  94. package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
  95. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  96. package/dist/store/postgres/reads.js +44 -87
  97. package/dist/store/postgres/record-writer.d.ts +4 -4
  98. package/dist/store/postgres/record-writer.js +9 -9
  99. package/dist/store/postgres/schema.d.ts +1408 -123
  100. package/dist/store/postgres/schema.js +194 -38
  101. package/dist/store/postgres/store.js +329 -79
  102. package/dist/store/postgres/tenant.js +1 -2
  103. package/dist/store/types.d.ts +155 -50
  104. package/dist/store/types.js +0 -1
  105. package/dist/tenant/advance.js +20 -8
  106. package/dist/tenant/agent-manifests.d.ts +72 -0
  107. package/dist/tenant/agent-manifests.js +359 -0
  108. package/dist/tenant/agent-versions.d.ts +126 -0
  109. package/dist/tenant/agent-versions.js +389 -0
  110. package/dist/tenant/agents.d.ts +230 -0
  111. package/dist/tenant/{environments.js → agents.js} +464 -320
  112. package/dist/tenant/auth.d.ts +1 -1
  113. package/dist/tenant/auth.js +3 -3
  114. package/dist/tenant/commands.d.ts +63 -2
  115. package/dist/tenant/commands.js +240 -83
  116. package/dist/tenant/context.d.ts +46 -19
  117. package/dist/tenant/context.js +6 -2
  118. package/dist/tenant/effects.js +7 -16
  119. package/dist/tenant/identity-file.d.ts +6 -6
  120. package/dist/tenant/identity-file.js +12 -10
  121. package/dist/tenant/issuers.js +3 -3
  122. package/dist/tenant/limits.d.ts +0 -3
  123. package/dist/tenant/limits.js +0 -3
  124. package/dist/tenant/memories.d.ts +38 -0
  125. package/dist/tenant/memories.js +119 -0
  126. package/dist/tenant/reset.js +2 -2
  127. package/dist/tenant/run-grants.d.ts +6 -0
  128. package/dist/tenant/run-grants.js +4 -0
  129. package/dist/tenant/runtime.d.ts +1 -1
  130. package/dist/tenant/runtime.js +8 -0
  131. package/dist/tenant/sessions.d.ts +28 -45
  132. package/dist/tenant/sessions.js +85 -208
  133. package/dist/tenant/status.js +6 -7
  134. package/dist/tenant/sweep.js +12 -3
  135. package/dist/tenant/transcript.d.ts +16 -16
  136. package/dist/tenant/triggers.d.ts +143 -0
  137. package/dist/tenant/triggers.js +715 -0
  138. package/dist/tenant/turn-timing.d.ts +11 -1
  139. package/dist/tenant/turn-timing.js +3 -1
  140. package/dist/tenant/types.d.ts +5 -0
  141. package/dist/tenant/worker.d.ts +2 -0
  142. package/dist/tenant/worker.js +5 -0
  143. package/dist/traces/map.d.ts +1 -1
  144. package/dist/traces/map.js +7 -1
  145. package/dist/tuning.d.ts +7 -0
  146. package/dist/tuning.js +19 -0
  147. package/dist/vault/service.d.ts +13 -8
  148. package/dist/vault/service.js +61 -17
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/package.json +13 -12
  152. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  153. package/dist/api/http/routes/environment-manifests.js +0 -118
  154. package/dist/api/http/routes/environments.d.ts +0 -11
  155. package/dist/api/http/routes/environments.js +0 -332
  156. package/dist/record/environment.d.ts +0 -27
  157. package/dist/record/environment.js +0 -46
  158. package/dist/tenant/environment-manifests.d.ts +0 -57
  159. package/dist/tenant/environment-manifests.js +0 -208
  160. package/dist/tenant/environments.d.ts +0 -172
@@ -1,5 +1,9 @@
1
- import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
1
+ import { DEFAULT_HARNESS, agentGranted } from "@nylorun/core/contracts";
2
2
  import { fail } from "./http.js";
3
+ /** Whether `access` reaches sessions on Agent `agentId`. */
4
+ export function reachesAgent(access, agentId) {
5
+ return access?.agents === undefined || agentGranted(access.agents, agentId);
6
+ }
3
7
  /**
4
8
  * `access`, when set, limits the request to the owner's sessions of the allowed agents:
5
9
  * anything else is the same 404 as a missing session, so a subject cannot learn which session
@@ -9,7 +13,7 @@ function owned(session, access) {
9
13
  if (!session ||
10
14
  (access !== undefined &&
11
15
  (session.ownerUserId !== access.owner ||
12
- (access.agents !== undefined && !access.agents.has(session.agentId)))))
16
+ !reachesAgent(access, session.agentId))))
13
17
  return fail(404, "Session not found");
14
18
  return session;
15
19
  }
@@ -161,23 +161,22 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
161
161
  if (exists)
162
162
  return;
163
163
  const definition = leafDefinition(workflow, body, request);
164
- // The tree's Environment reaches every agent in it: its sandbox and its vault.
164
+ // The flow's Agent (Q6) reaches every agent in the tree: its sandbox and its vault. Each leaf
165
+ // session runs on it, with the flow's manifest version, and its leaf agent's loop manifest.
165
166
  const sandbox = workflow.sandbox;
166
167
  const created = {
167
168
  id: agentSessionId,
168
- agentId: body.agentId,
169
+ agentId: workflow.agentId,
169
170
  ownerUserId: workflow.ownerUserId,
170
171
  manifest: definition.manifest,
171
172
  manifestHash: definition.manifestHash,
172
- definitionHash: definition.manifestHash,
173
- implementationVersion: definition.implementationVersion,
173
+ ...(workflow.definition === undefined ? {} : { definition: workflow.definition }),
174
174
  status: "idle",
175
175
  activeTurnId: null,
176
176
  creation: {
177
177
  requestId: `flow-put-${request.effectId}`,
178
- agentId: body.agentId,
178
+ agent: workflow.agentId,
179
179
  ownerUserId: workflow.ownerUserId,
180
- ...(workflow.environmentId === undefined ? {} : { environment: workflow.environmentId }),
181
180
  },
182
181
  flowRootId: workflow.flowRootId ?? workflow.id,
183
182
  ...(workflow.environmentId === undefined
@@ -262,11 +261,7 @@ function leafDefinition(workflow, body, request) {
262
261
  const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
263
262
  if (!flow)
264
263
  return fail(404, `Agent '${body.agentId}' is not a flow agent used as a tool`);
265
- return {
266
- manifest: flow,
267
- manifestHash: hashManifest(flow),
268
- implementationVersion: workflow.implementationVersion,
269
- };
264
+ return { manifest: flow, manifestHash: hashManifest(flow) };
270
265
  }
271
266
  const leaf = isWorkflowManifest(workflow.manifest)
272
267
  ? embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId)
@@ -274,9 +269,5 @@ function leafDefinition(workflow, body, request) {
274
269
  if (!leaf || isWorkflowManifest(leaf))
275
270
  return fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
276
271
  const manifest = leaf;
277
- return {
278
- manifest,
279
- manifestHash: hashManifest(manifest),
280
- implementationVersion: workflow.implementationVersion,
281
- };
272
+ return { manifest, manifestHash: hashManifest(manifest) };
282
273
  }
@@ -15,9 +15,8 @@
15
15
  * # Optional, with their defaults:
16
16
  * subject: "{sub}" # scalar claims only
17
17
  * scopes: { claim: scope } # or { fixed: [sessions:own, agents:read] }
18
- * allowedScopes: [agents:read, sessions:own, environments:write] # add studio to grant it
19
- * agents: [support] # absent reaches every agent
20
- * environments: ["{org_id}/*"] # absent reaches no Environment
18
+ * allowedScopes: [agents:read, sessions:own, agents:write] # add studio to grant it
19
+ * agents: ["{org_id}/*", support] # Agent grants; absent reaches no Agent
21
20
  * jwksFetch: # how the JWKS is fetched and cached
22
21
  * timeoutMs: 5000 # one fetch
23
22
  * maxBytes: 262144 # the largest JWKS read
@@ -74,10 +73,11 @@ export interface TrustedIssuerConfig {
74
73
  };
75
74
  /** The scopes its tokens may carry; others are dropped. */
76
75
  readonly allowedScopes: readonly IssuerScope[];
77
- /** The agents its tokens reach; absent reaches all. */
76
+ /**
77
+ * Agent grant templates (protocol 16; `agents` and `environments` before), such as `{org_id}/*`,
78
+ * `support` or `*`: what its tokens reach. Absent reaches no Agent.
79
+ */
78
80
  readonly agents?: readonly string[];
79
- /** Environment grant templates, such as `{org_id}/*`; absent reaches no Environment. */
80
- readonly environments?: readonly string[];
81
81
  /** How its JWKS is fetched; each setting left out is its default (`JWKS_FETCH_DEFAULTS`). */
82
82
  readonly jwksFetch?: Partial<JwksFetchSettings>;
83
83
  }
@@ -15,9 +15,8 @@
15
15
  * # Optional, with their defaults:
16
16
  * subject: "{sub}" # scalar claims only
17
17
  * scopes: { claim: scope } # or { fixed: [sessions:own, agents:read] }
18
- * allowedScopes: [agents:read, sessions:own, environments:write] # add studio to grant it
19
- * agents: [support] # absent reaches every agent
20
- * environments: ["{org_id}/*"] # absent reaches no Environment
18
+ * allowedScopes: [agents:read, sessions:own, agents:write] # add studio to grant it
19
+ * agents: ["{org_id}/*", support] # Agent grants; absent reaches no Agent
21
20
  * jwksFetch: # how the JWKS is fetched and cached
22
21
  * timeoutMs: 5000 # one fetch
23
22
  * maxBytes: 262144 # the largest JWKS read
@@ -29,7 +28,7 @@
29
28
  import { createPublicKey } from "node:crypto";
30
29
  import { parse as parseYaml } from "yaml";
31
30
  import { z } from "zod";
32
- import { ISSUER_SCOPES, isEnvironmentGrant, TIMER_MAX_MS, TOKEN_SCOPES, } from "@nylorun/core/contracts";
31
+ import { ISSUER_SCOPES, isAgentGrant, TIMER_MAX_MS, TOKEN_SCOPES, } from "@nylorun/core/contracts";
33
32
  /** The JWT algorithms an issuer may sign with. */
34
33
  export const ISSUER_ALGORITHMS = ["RS256", "ES256", "EdDSA"];
35
34
  /** An entry's defaults: the `sub` claim, the RFC 9068 `scope` claim, every token scope but `studio`. */
@@ -135,11 +134,10 @@ const IssuerEntrySchema = z
135
134
  ], { error: "must be { claim: <name> } or { fixed: [<scope>, ...] }" })
136
135
  .default(DEFAULT_SCOPES),
137
136
  allowedScopes: z.array(IssuerScopeSchema).min(1).default([...DEFAULT_ALLOWED_SCOPES]),
138
- agents: z.array(z.string().min(1)).optional(),
139
- environments: z
140
- .array(template("an Environment grant").refine(
137
+ agents: z
138
+ .array(template("an Agent grant").refine(
141
139
  // A rendered grant must be a grant: checked here with a sample value per claim.
142
- (value) => isEnvironmentGrant(renderTemplate(value, () => "x")), "must render to an Environment id or a prefix ending in /*, as {org_id}/*"))
140
+ (value) => isAgentGrant(renderTemplate(value, () => "x")), "must render to *, an Agent id or a prefix ending in /*, as {org_id}/*"))
143
141
  .optional(),
144
142
  jwksFetch: z
145
143
  .object({
@@ -208,7 +206,12 @@ export function parseIdentityFile(text, source = "identity file", options = {})
208
206
  entry?.scopes,
209
207
  ]);
210
208
  if (scopesNamed.includes('"sandboxes:write"'))
211
- return fail(`${where}: sandboxes:write: renamed environments:write in protocol 11`);
209
+ return fail(`${where}: sandboxes:write: renamed agents:write (environments:write in protocol 11)`);
210
+ // Protocol 16 renamed these: refused, never read as no grants.
211
+ if (entry && typeof entry === "object" && "environments" in entry)
212
+ return fail(`${where}: environments: Environments are Agents in protocol 16: move the grants to agents`);
213
+ if (scopesNamed.includes('"environments:write"'))
214
+ return fail(`${where}: environments:write: renamed agents:write in protocol 16`);
212
215
  const parsed = IssuerEntrySchema.safeParse(entry);
213
216
  if (!parsed.success)
214
217
  return fail(`${where}: ${issueText(parsed.error.issues)}`);
@@ -244,7 +247,6 @@ export function parseIdentityFile(text, source = "identity file", options = {})
244
247
  scopes: value.scopes,
245
248
  allowedScopes: [...new Set(value.allowedScopes)],
246
249
  ...(value.agents ? { agents: [...new Set(value.agents)] } : {}),
247
- ...(value.environments ? { environments: value.environments } : {}),
248
250
  });
249
251
  });
250
252
  return issuers;
@@ -299,7 +299,7 @@ export async function verifyIssuerToken(ctx, issuer, raw, resourceMetadata) {
299
299
  const subject = renderTemplate(config.subject, (claim) => scalarClaim(payload, claim));
300
300
  if (subject === undefined || !isSubject(subject) || RESERVED.has(subject))
301
301
  return rejected("issuer_token_subject");
302
- const environments = config.environments?.flatMap((grant) => {
302
+ const agents = (config.agents ?? []).flatMap((grant) => {
303
303
  const rendered = renderTemplate(grant, (claim) => {
304
304
  const value = scalarClaim(payload, claim);
305
305
  return value !== undefined && GRANT_SEGMENT.test(value) ? value : undefined;
@@ -316,8 +316,8 @@ export async function verifyIssuerToken(ctx, issuer, raw, resourceMetadata) {
316
316
  issuer: config.name,
317
317
  subject,
318
318
  scopes: scopesOf(config, payload),
319
- agents: config.agents ? new Set(config.agents) : "*",
320
- environments: [...new Set(environments ?? [])],
319
+ // `*` reaches every Agent; no grant reaches none.
320
+ agents: agents.includes("*") ? "*" : [...new Set(agents)],
321
321
  expiresAt: payload.exp * 1000,
322
322
  tokenId: jti,
323
323
  ...(verdict.kid !== undefined ? { keyId: verdict.kid } : {}),
@@ -78,9 +78,6 @@ export declare function budgetIssues(budgets: readonly unknown[], limits: Runtim
78
78
  * Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
79
79
  * slots for (the Runtime limit `environmentSecretSlots`).
80
80
  */
81
- export declare function requiresIssues(requires: {
82
- readonly secrets?: readonly string[];
83
- } | undefined, limits: RuntimeLimits): string[];
84
81
  /**
85
82
  * Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
86
83
  * its setup commands, their lengths and timeouts, its input file entries and the files of each,
@@ -137,9 +137,6 @@ export function budgetIssues(budgets, limits) {
137
137
  * Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
138
138
  * slots for (the Runtime limit `environmentSecretSlots`).
139
139
  */
140
- export function requiresIssues(requires, limits) {
141
- return more("requires.secrets", requires?.secrets?.length ?? 0, "environmentSecretSlots", limits);
142
- }
143
140
  /**
144
141
  * Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
145
142
  * its setup commands, their lengths and timeouts, its input file entries and the files of each,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
3
+ * list and read them, write one whole (create or replace, with an optional `content_sha256`
4
+ * precondition), delete one or a folder, and read the versions every change leaves. Writes go
5
+ * through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
6
+ * and versions hold; the actor is the caller (`human:<subject>`, else `application`).
7
+ */
8
+ import { type Memory, type MemoryList, type MemoryVersionDetail, type MemoryVersionPage, type PutMemoryRequest } from "@nylorun/core/contracts";
9
+ import type { AuthScope, TenantContext } from "./context.js";
10
+ /** Who an API write names: the person a request acts for, else the application. */
11
+ export declare function memoryActorOf(scope: AuthScope): string;
12
+ /** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
13
+ export declare function listMemories(ctx: TenantContext, agentId: string, options: {
14
+ pathPrefix?: string;
15
+ cursor?: string;
16
+ limit: number;
17
+ }): Promise<MemoryList>;
18
+ /** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
19
+ export declare function readMemory(ctx: TenantContext, agentId: string, rawPath: string): Promise<Memory>;
20
+ /**
21
+ * `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
22
+ * `contentSha256` the write applies only while the memory still has that content (`""`: only while
23
+ * no memory has the path); otherwise `409 memory_conflict` with the current memory.
24
+ */
25
+ export declare function putMemoryContent(ctx: TenantContext, agentId: string, rawPath: string, body: PutMemoryRequest, scope: AuthScope): Promise<Memory>;
26
+ /** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
27
+ export declare function deleteMemoryPath(ctx: TenantContext, agentId: string, rawPath: string, scope: AuthScope): Promise<{
28
+ path: string;
29
+ deleted: number;
30
+ }>;
31
+ /** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
32
+ export declare function listMemoryVersions(ctx: TenantContext, agentId: string, options: {
33
+ path?: string;
34
+ cursor?: string;
35
+ limit: number;
36
+ }): Promise<MemoryVersionPage>;
37
+ /** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
38
+ export declare function readMemoryVersion(ctx: TenantContext, agentId: string, versionId: string): Promise<MemoryVersionDetail>;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * An Agent's memories over the Runtime API (track T5, D72), shaped like Anthropic's memory stores:
3
+ * list and read them, write one whole (create or replace, with an optional `content_sha256`
4
+ * precondition), delete one or a folder, and read the versions every change leaves. Writes go
5
+ * through the memory service (`memory/service.ts`), as a model's do, so the same paths, limits
6
+ * and versions hold; the actor is the caller (`human:<subject>`, else `application`).
7
+ */
8
+ import { memoryPathOf, } from "@nylorun/core/contracts";
9
+ import { applyMemoryCommand, memorySummaryOf, memoryVersionDetailOf, memoryVersionOf, } from "../memory/service.js";
10
+ import { liveEnvironment } from "./agents.js";
11
+ import { fail } from "./http.js";
12
+ import { readRuntimeLimits } from "./runtime-limits.js";
13
+ /** Who an API write names: the person a request acts for, else the application. */
14
+ export function memoryActorOf(scope) {
15
+ return scope.kind === "token" || scope.kind === "subject" ? `human:${scope.subject}` : "application";
16
+ }
17
+ async function agentOf(t, agentId) {
18
+ if (!(await liveEnvironment(t, agentId)))
19
+ fail(404, `Agent ${agentId} not found`, { code: "not_found" });
20
+ }
21
+ /** A path from a route: relative to `/memories` (as the route's segment), or absolute. */
22
+ function routePath(raw, kind) {
23
+ const trimmed = raw.replace(/^\/+/, "");
24
+ const parsed = memoryPathOf(trimmed.startsWith("memories/") || trimmed === "memories" ? trimmed : `memories/${trimmed}`, kind);
25
+ return "error" in parsed ? fail(400, parsed.error, { code: "invalid_request" }) : parsed.path;
26
+ }
27
+ /** The HTTP answer to a refused write. */
28
+ function refusal(result) {
29
+ switch (result.code) {
30
+ case "memory.not_found":
31
+ return fail(404, result.text, { code: "not_found" });
32
+ case "memory.limit":
33
+ return fail(409, result.text, { code: "limit_exceeded", details: { limit: "memoriesPerAgent" } });
34
+ case "memory.too_large":
35
+ return fail(413, result.text, { code: "limit_exceeded", details: { limit: "memoryBytes" } });
36
+ case "memory.exists":
37
+ return fail(409, result.text, { code: "memory_conflict" });
38
+ default:
39
+ return fail(400, result.text, { code: "invalid_request" });
40
+ }
41
+ }
42
+ /** `GET /v1/agents/{id}/memories`: the Agent's memories by path, under `pathPrefix` when given. */
43
+ export async function listMemories(ctx, agentId, options) {
44
+ const folder = options.pathPrefix === undefined ? undefined : routePath(options.pathPrefix, "any");
45
+ return ctx.store.tx(async (t) => {
46
+ await agentOf(t, agentId);
47
+ const rows = await t.memories(agentId, {
48
+ ...(folder === undefined ? {} : { folder }),
49
+ ...(options.cursor === undefined ? {} : { after: options.cursor }),
50
+ limit: options.limit + 1,
51
+ });
52
+ const page = rows.slice(0, options.limit);
53
+ return { memories: page.map(memorySummaryOf), nextCursor: rows.length > options.limit ? page.at(-1).path : null };
54
+ });
55
+ }
56
+ /** `GET /v1/agents/{id}/memories/{path}`: one memory with its content. */
57
+ export async function readMemory(ctx, agentId, rawPath) {
58
+ const path = routePath(rawPath, "file");
59
+ return ctx.store.tx(async (t) => {
60
+ await agentOf(t, agentId);
61
+ const row = (await t.memory(agentId, path)) ?? fail(404, `The path ${path} does not exist`, { code: "not_found" });
62
+ return { ...memorySummaryOf(row), content: row.content };
63
+ });
64
+ }
65
+ /**
66
+ * `PUT /v1/agents/{id}/memories/{path}`: writes the memory whole, as the tool's `create` does. With
67
+ * `contentSha256` the write applies only while the memory still has that content (`""`: only while
68
+ * no memory has the path); otherwise `409 memory_conflict` with the current memory.
69
+ */
70
+ export async function putMemoryContent(ctx, agentId, rawPath, body, scope) {
71
+ const path = routePath(rawPath, "file");
72
+ return ctx.store.tx(async (t) => {
73
+ await agentOf(t, agentId);
74
+ const current = await t.memory(agentId, path, { lock: true });
75
+ if (body.contentSha256 !== undefined && body.contentSha256 !== (current?.contentSha256 ?? ""))
76
+ fail(409, `${path} has changed since that content_sha256: read it again and write on top of what it holds now.`, {
77
+ code: "memory_conflict",
78
+ details: { current: current ? { ...memorySummaryOf(current), content: current.content } : null },
79
+ });
80
+ const result = await applyMemoryCommand(t, agentId, { command: "create", path, fileText: body.content }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
81
+ if (!result.ok)
82
+ refusal(result);
83
+ const row = (await t.memory(agentId, path));
84
+ return { ...memorySummaryOf(row), content: row.content };
85
+ });
86
+ }
87
+ /** `DELETE /v1/agents/{id}/memories/{path}`: deletes a memory, or every memory in a folder. */
88
+ export async function deleteMemoryPath(ctx, agentId, rawPath, scope) {
89
+ const path = routePath(rawPath, "any");
90
+ return ctx.store.tx(async (t) => {
91
+ await agentOf(t, agentId);
92
+ const result = await applyMemoryCommand(t, agentId, { command: "delete", path }, { actor: memoryActorOf(scope) }, await readRuntimeLimits(t));
93
+ if (!result.ok)
94
+ refusal(result);
95
+ return { path, deleted: result.changes.length };
96
+ });
97
+ }
98
+ /** `GET /v1/agents/{id}/memory_versions`: versions, newest first; `path` keeps one path's. */
99
+ export async function listMemoryVersions(ctx, agentId, options) {
100
+ const path = options.path === undefined ? undefined : routePath(options.path, "file");
101
+ return ctx.store.tx(async (t) => {
102
+ await agentOf(t, agentId);
103
+ const rows = await t.memoryVersions(agentId, {
104
+ ...(path === undefined ? {} : { path }),
105
+ ...(options.cursor === undefined ? {} : { before: options.cursor }),
106
+ limit: options.limit + 1,
107
+ });
108
+ const page = rows.slice(0, options.limit);
109
+ return { versions: page.map(memoryVersionOf), nextCursor: rows.length > options.limit ? page.at(-1).id : null };
110
+ });
111
+ }
112
+ /** `GET /v1/agents/{id}/memory_versions/{versionId}`: one version with the content it left. */
113
+ export async function readMemoryVersion(ctx, agentId, versionId) {
114
+ return ctx.store.tx(async (t) => {
115
+ await agentOf(t, agentId);
116
+ const row = (await t.memoryVersion(agentId, versionId)) ?? fail(404, `Memory version ${versionId} not found`, { code: "not_found" });
117
+ return memoryVersionDetailOf(row);
118
+ });
119
+ }
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { detachAllSessions } from "./environments.js";
3
+ import { detachAllSessions } from "./agents.js";
4
4
  import { contentKey } from "../artifacts/folders.js";
5
5
  /**
6
6
  * Rename `dir` to a sibling, delete the sibling, recreate an empty `dir`.
@@ -38,7 +38,7 @@ function replaceDirectory(dir) {
38
38
  */
39
39
  export async function resetTenant(ctx, scope) {
40
40
  const clearSessions = scope === "sessions" || scope === "all";
41
- const clearSandboxes = scope === "environments" || scope === "all";
41
+ const clearSandboxes = scope === "agents" || scope === "all";
42
42
  const clearAll = scope === "all";
43
43
  if (clearSandboxes) {
44
44
  await ctx.sandbox.removeAll();
@@ -23,6 +23,12 @@ export interface RunGrants extends RunTokens {
23
23
  }
24
24
  export declare function createRunGrants(): RunGrants;
25
25
  type GrantContext = Pick<TenantContext, "keys" | "config" | "runGrants">;
26
+ /** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
27
+ export declare function rootAgentOf(session: {
28
+ readonly manifest: {
29
+ readonly id: string;
30
+ };
31
+ }): string;
26
32
  /** What a run token names of the session: its root agent and the turn the advance runs. */
27
33
  export interface RunOf {
28
34
  readonly agentId: string;
@@ -18,6 +18,10 @@ export function createRunGrants() {
18
18
  },
19
19
  };
20
20
  }
21
+ /** The root agent a session's run token names: its loop manifest's id (the agent manifest's name). */
22
+ export function rootAgentOf(session) {
23
+ return session.manifest.id;
24
+ }
21
25
  /**
22
26
  * Mints and registers the run token of the advance that just took `lease`. Nothing when the
23
27
  * gates run in this process, or the session has no active turn (it makes no gate call).
@@ -81,7 +81,7 @@ export type TenantOpenHooks = {
81
81
  harnessTap?: MemoryPortsOptions["tap"];
82
82
  /**
83
83
  * Sandbox pods (F7.2): the sandboxes service (`NYLORUN_SANDBOXES_URL`) and the Runtime image
84
- * pods copy the engine from. Without it, kind `pod` is `environment_unavailable`.
84
+ * pods copy the engine from. Without it, kind `pod` is `agent_unavailable`.
85
85
  */
86
86
  pods?: TenantPods;
87
87
  /** How the trace route makes spans (R7.5): the Host's `OTEL_*` resource and content opt-ins. */
@@ -32,6 +32,7 @@ import { localWorkspace, remoteWorkspace, withPodWorkspaces, } from "../harness-
32
32
  import { hostAuthority } from "../sandbox/join.js";
33
33
  import { harnessOfToken } from "./harness-tokens.js";
34
34
  import { reconcileSandbox } from "../sandbox/pods/reconcile.js";
35
+ import { fireTimer } from "./triggers.js";
35
36
  import { hostTuning } from "../tuning.js";
36
37
  function validateConfig(config) {
37
38
  for (const key of ["leaseMs", "ownerLeaseMs", "sweepIntervalMs"]) {
@@ -266,6 +267,12 @@ export class TenantRuntime {
266
267
  await execution.sandbox?.(config.tenantId, sandboxId, signal);
267
268
  return true;
268
269
  },
270
+ timer: async (key, at) => {
271
+ if (ctx.closing || ctx.closed)
272
+ return false;
273
+ await execution.timer(config.tenantId, key, at);
274
+ return true;
275
+ },
269
276
  send: (request) => request.kind === "wake"
270
277
  ? ctx.wake(request.sessionId, request.wake)
271
278
  : ctx.sandboxSignal(request.sandboxId, request.signal),
@@ -322,6 +329,7 @@ export class TenantRuntime {
322
329
  advance: (sessionId, signal) => advance(ctx, sessionId, signal),
323
330
  sweep: () => sweep(ctx),
324
331
  sandbox: (sandboxId, trigger) => reconcileSandbox(ctx, sandboxId, trigger),
332
+ fire: (key) => fireTimer(ctx, key),
325
333
  };
326
334
  const unregister = workers.register(config.tenantId, worker);
327
335
  detach = async () => {
@@ -1,42 +1,16 @@
1
1
  /**
2
- * Session and definition resources: `PUT/GET /v1/agents`, `PUT /v1/sessions/:id` (create, or
3
- * find an identical one), the session list and the session view. Reads use the store's typed
4
- * queries; `PUT /v1/sessions/:id` runs in one transaction with the Environment's checks.
2
+ * Session resources: `PUT /v1/sessions/:id` (create, or find an identical one), the session list
3
+ * and the session view. A session runs on one Agent (protocol 16) and pins the manifest version
4
+ * the Agent runs when it opens; each turn takes the version the Agent runs then
5
+ * (`agent-versions.ts`). Reads use the store's typed queries; `PUT /v1/sessions/:id` runs in
6
+ * one transaction with the Agent's checks.
5
7
  */
6
- import { type PutAgentRequest, type PutSessionRequest } from "@nylorun/core/contracts";
8
+ import { type PutSessionRequest } from "@nylorun/core/contracts";
7
9
  import type { Tx } from "../store/types.js";
8
10
  import { type Session, type SessionAccess, type TenantContext } from "./context.js";
9
- export declare function listDefinitions(ctx: TenantContext): Promise<{
10
- agents: {
11
- agentId: any;
12
- manifest: any;
13
- manifestHash: string;
14
- implementationVersion: string;
15
- }[];
16
- }>;
17
- /**
18
- * The agents a token caller may see: id, name and description only, never
19
- * instructions, tools or MCP servers, and only the agents allowed.
20
- */
21
- export declare function listAgentsPublic(ctx: TenantContext, agents: ReadonlySet<string> | "*"): Promise<{
22
- agents: {
23
- agentId: string;
24
- name?: any;
25
- description?: any;
26
- }[];
27
- }>;
28
- export declare function putDefinition(ctx: TenantContext, agentId: string, body: PutAgentRequest, options?: {
29
- /** Run every check and save nothing (`?dryRun=true`, protocol 14). */
30
- dryRun?: boolean;
31
- }): Promise<{
32
- agentId: string;
33
- manifestHash: string;
34
- implementationVersion: string;
35
- dryRun?: boolean | undefined;
36
- }>;
37
11
  /**
38
- * `access`, when the request acts for a person, limits the list to that person's sessions of
39
- * the agents they may use.
12
+ * `access`, when the request acts for a person, limits the list to that person's sessions on
13
+ * the Agents they may use.
40
14
  */
41
15
  export declare function listSessions(ctx: TenantContext, agentId: string | null, access?: SessionAccess): Promise<{
42
16
  sessions: {
@@ -48,26 +22,35 @@ export declare function listSessions(ctx: TenantContext, agentId: string | null,
48
22
  }[];
49
23
  }>;
50
24
  /**
51
- * Create a session from its definition, or find an identical one. `access`, when the request
52
- * acts for a subject, must be the session's owner: another owner's session id is the 404 of a
53
- * missing one (not the 409 of a mismatch), and so is an Environment whose owner guard names
54
- * another owner.
25
+ * Create a session on an Agent, or find an identical one. `access`, when the request acts for a
26
+ * subject, must be the session's owner: another owner's session id is the 404 of a missing one
27
+ * (not the 409 of a mismatch), and so is an Agent whose owner guard names another owner.
55
28
  *
56
- * The session's Environment is fixed for its life: its sandbox (kinds `virtual` and `pod`) is
57
- * the session's, with the spec of the Environment's applied recipe pinned now, and its vault
58
- * holds the session's credentials. Without one the session has no sandbox and no credentials.
29
+ * The session runs on its Agent for its life: the Agent's sandbox (kinds `virtual` and `pod`) is
30
+ * the session's, with the spec of what the Agent runs pinned now, and the Agent's vault holds the
31
+ * session's credentials. It pins the manifest version the Agent runs (`definition`), as the loop
32
+ * manifest that version's harness section makes, and the harness that section names (`pinOf`);
33
+ * a turn's first segment pins the version the Agent runs then (`repinTurn`).
59
34
  */
60
35
  export declare function putSession(ctx: TenantContext, id: string, body: PutSessionRequest, access?: SessionAccess, options?: {
61
36
  /**
62
37
  * Create the session if it is missing and otherwise leave it as it is (an AG-UI thread's
63
- * session): an existing one must have the same owner and agent, or it is a 404.
38
+ * session): an existing one must have the same owner and Agent, or it is a 404.
64
39
  */
65
40
  createOnly?: boolean;
66
41
  /**
67
- * The Environments the caller reaches (a token caller's grants, `environmentGrantsOf`);
68
- * undefined reaches every one. `environment` must name one of them.
42
+ * The Agents the caller reaches (a token caller's grants, `agentGrantsOf`); undefined
43
+ * reaches every one. `agent` must name one of them.
69
44
  */
70
- environmentGrants?: readonly string[];
45
+ agentGrants?: readonly string[];
46
+ }): Promise<Session>;
47
+ /**
48
+ * `putSession`'s work in transaction `t`: also how a trigger's first run opens the session it
49
+ * names (track T4), in the run's transaction.
50
+ */
51
+ export declare function openSessionIn(ctx: TenantContext, t: Tx, id: string, body: PutSessionRequest, access?: SessionAccess, options?: {
52
+ createOnly?: boolean;
53
+ agentGrants?: readonly string[];
71
54
  }): Promise<Session>;
72
55
  /** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
73
56
  export declare function sessionView(t: Tx, s: Session): Promise<unknown>;