@cursor/july 0.1.48 → 0.1.51

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 (151) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +7 -14
  3. package/dist/bin/agent-serve.js +22 -8
  4. package/dist/channels/slack/init.d.ts +2 -3
  5. package/dist/channels/slack/init.d.ts.map +1 -1
  6. package/dist/channels/slack/init.js +8 -43
  7. package/dist/channels/slack/manifest.d.ts +3 -3
  8. package/dist/channels/slack/manifest.js +3 -3
  9. package/dist/channels/slack/setup.d.ts +2 -11
  10. package/dist/channels/slack/setup.d.ts.map +1 -1
  11. package/dist/channels/slack/setup.js +26 -94
  12. package/dist/channels/slack/types.d.ts +3 -4
  13. package/dist/channels/slack/types.d.ts.map +1 -1
  14. package/dist/docs/404.html +2 -2
  15. package/dist/docs/ab.html +5 -5
  16. package/dist/docs/assets/{ab.md.hdxsvX5M.js → ab.md.DYjwREAP.js} +2 -2
  17. package/dist/docs/assets/{ab.md.hdxsvX5M.lean.js → ab.md.DYjwREAP.lean.js} +1 -1
  18. package/dist/docs/assets/{app.BC1P20xI.js → app.C51TX1Bw.js} +1 -1
  19. package/dist/docs/assets/chunks/@localSearchIndexroot.D2DpXdZN.js +1 -0
  20. package/dist/docs/assets/chunks/{VPLocalSearchBox.C_900xn2.js → VPLocalSearchBox.BB9HCJPq.js} +1 -1
  21. package/dist/docs/assets/chunks/{theme.CT135Jhh.js → theme.Dp-0SZcu.js} +2 -2
  22. package/dist/docs/assets/{deployment.md.Cdp4rTE4.js → deployment.md.B8kW-h7P.js} +1 -1
  23. package/dist/docs/assets/{evals.md.qeoGgPw0.js → evals.md.CVe_O75-.js} +3 -3
  24. package/dist/docs/assets/{evals.md.qeoGgPw0.lean.js → evals.md.CVe_O75-.lean.js} +1 -1
  25. package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.js → example-agents_benny.md.B-LIDGja.js} +1 -1
  26. package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.js → example-agents_oncall.md.CBmyuAKc.js} +2 -2
  27. package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.js → guides_human-in-the-loop.md.Cf8kIIqC.js} +2 -2
  28. package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.lean.js → guides_human-in-the-loop.md.Cf8kIIqC.lean.js} +1 -1
  29. package/dist/docs/assets/{guides_slack.md.zriQpU_9.js → guides_slack.md.D2nTRu9M.js} +7 -12
  30. package/dist/docs/assets/guides_slack.md.D2nTRu9M.lean.js +1 -0
  31. package/dist/docs/assets/{reference_cli.md.BDmjPPAm.js → reference_cli.md.CGKfz_Yj.js} +3 -7
  32. package/dist/docs/assets/{reference_cli.md.BDmjPPAm.lean.js → reference_cli.md.CGKfz_Yj.lean.js} +1 -1
  33. package/dist/docs/assets/{reference_http-api.md.DGrw_wOu.js → reference_http-api.md.DMbdFGVQ.js} +1 -1
  34. package/dist/docs/assets/{storage.md.CJYd5YmN.js → storage.md.D8NQ389l.js} +3 -3
  35. package/dist/docs/assets/storage.md.D8NQ389l.lean.js +1 -0
  36. package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.js → troubleshooting.md.C6n3wCr6.js} +1 -1
  37. package/dist/docs/building-with-agents.html +3 -3
  38. package/dist/docs/concepts.html +3 -3
  39. package/dist/docs/deployment.html +5 -5
  40. package/dist/docs/evals.html +6 -6
  41. package/dist/docs/example-agents/approval-buddy.html +3 -3
  42. package/dist/docs/example-agents/benny.html +5 -5
  43. package/dist/docs/example-agents/bugbot.html +3 -3
  44. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  45. package/dist/docs/example-agents/codeowners-review.html +3 -3
  46. package/dist/docs/example-agents/concierge.html +3 -3
  47. package/dist/docs/example-agents/fsd.html +3 -3
  48. package/dist/docs/example-agents/index.html +3 -3
  49. package/dist/docs/example-agents/knowledge-base.html +3 -3
  50. package/dist/docs/example-agents/oncall.html +6 -6
  51. package/dist/docs/example-agents/security-reviewer.html +3 -3
  52. package/dist/docs/example-agents/slack-agent.html +3 -3
  53. package/dist/docs/example-agents/weather-agent.html +3 -3
  54. package/dist/docs/guides/agent-to-agent.html +3 -3
  55. package/dist/docs/guides/cloud-runtime.html +3 -3
  56. package/dist/docs/guides/github.html +3 -3
  57. package/dist/docs/guides/human-in-the-loop.html +5 -5
  58. package/dist/docs/guides/mcp-oauth.html +3 -3
  59. package/dist/docs/guides/opentelemetry.html +3 -3
  60. package/dist/docs/guides/slack.html +11 -16
  61. package/dist/docs/guides/webhooks.html +3 -3
  62. package/dist/docs/hashmap.json +1 -1
  63. package/dist/docs/hillclimbing.html +3 -3
  64. package/dist/docs/index.html +3 -3
  65. package/dist/docs/quickstart.html +3 -3
  66. package/dist/docs/reference/agent-config.html +3 -3
  67. package/dist/docs/reference/artifacts.html +3 -3
  68. package/dist/docs/reference/channels.html +3 -3
  69. package/dist/docs/reference/cli.html +6 -10
  70. package/dist/docs/reference/connections.html +3 -3
  71. package/dist/docs/reference/hooks.html +3 -3
  72. package/dist/docs/reference/http-api.html +5 -5
  73. package/dist/docs/reference/instructions.html +3 -3
  74. package/dist/docs/reference/playground.html +3 -3
  75. package/dist/docs/reference/project-layout.html +3 -3
  76. package/dist/docs/reference/prompt.html +3 -3
  77. package/dist/docs/reference/schedules.html +3 -3
  78. package/dist/docs/reference/sessions.html +3 -3
  79. package/dist/docs/reference/skills.html +3 -3
  80. package/dist/docs/reference/subagents.html +3 -3
  81. package/dist/docs/reference/tools.html +3 -3
  82. package/dist/docs/scaffolding-agents.html +3 -3
  83. package/dist/docs/storage.html +5 -5
  84. package/dist/docs/templates/triage.html +3 -3
  85. package/dist/docs/troubleshooting.html +5 -5
  86. package/dist/internal/cli-ax.js +3 -3
  87. package/dist/internal/cli-cursor.d.ts +18 -0
  88. package/dist/internal/cli-cursor.d.ts.map +1 -1
  89. package/dist/internal/cli-cursor.js +25 -0
  90. package/dist/internal/cli-slack.d.ts +3 -4
  91. package/dist/internal/cli-slack.d.ts.map +1 -1
  92. package/dist/internal/cli-slack.js +126 -325
  93. package/dist/internal/convert-automation/convert-workflow.js +2 -2
  94. package/dist/internal/event-log.d.ts +17 -0
  95. package/dist/internal/event-log.d.ts.map +1 -1
  96. package/dist/internal/event-log.js +32 -2
  97. package/dist/internal/process-memory-telemetry.d.ts +90 -0
  98. package/dist/internal/process-memory-telemetry.d.ts.map +1 -0
  99. package/dist/internal/process-memory-telemetry.js +259 -0
  100. package/dist/internal/server.d.ts.map +1 -1
  101. package/dist/internal/server.js +9 -10
  102. package/dist/internal/session-engine.d.ts +4 -0
  103. package/dist/internal/session-engine.d.ts.map +1 -1
  104. package/dist/internal/session-engine.js +19 -5
  105. package/dist/internal/slack-provision-client.d.ts +41 -6
  106. package/dist/internal/slack-provision-client.d.ts.map +1 -1
  107. package/dist/internal/slack-provision-client.js +53 -7
  108. package/dist/playground/assets/{index-DEVTyeOu.js → index-BKMz70wx.js} +41 -41
  109. package/dist/playground/assets/index-BmMrulSc.css +1 -0
  110. package/dist/playground/index.html +2 -2
  111. package/dist/storage.d.ts +36 -10
  112. package/dist/storage.d.ts.map +1 -1
  113. package/dist/storage.js +82 -6
  114. package/docs/ab.md +4 -3
  115. package/docs/deployment.md +1 -1
  116. package/docs/evals.md +10 -7
  117. package/docs/example-agents/benny.md +2 -3
  118. package/docs/example-agents/oncall.md +4 -5
  119. package/docs/guides/human-in-the-loop.md +1 -1
  120. package/docs/guides/slack.md +51 -112
  121. package/docs/reference/cli.md +14 -37
  122. package/docs/reference/http-api.md +4 -3
  123. package/docs/storage.md +38 -22
  124. package/docs/troubleshooting.md +2 -2
  125. package/package.json +1 -1
  126. package/skills/create-agent/SKILL.md +4 -5
  127. package/skills/setup-slack/SKILL.md +74 -132
  128. package/src/bin/agent-serve.ts +26 -9
  129. package/src/channels/slack/init.ts +10 -71
  130. package/src/channels/slack/manifest.ts +3 -3
  131. package/src/channels/slack/setup.ts +25 -103
  132. package/src/channels/slack/types.ts +3 -4
  133. package/src/internal/cli-ax.ts +3 -3
  134. package/src/internal/cli-cursor.ts +34 -0
  135. package/src/internal/cli-slack.ts +136 -394
  136. package/src/internal/convert-automation/convert-workflow.ts +2 -2
  137. package/src/internal/event-log.ts +45 -7
  138. package/src/internal/process-memory-telemetry.ts +373 -0
  139. package/src/internal/server.ts +13 -0
  140. package/src/internal/session-engine.ts +20 -1
  141. package/src/internal/slack-provision-client.ts +90 -9
  142. package/src/storage.ts +130 -11
  143. package/dist/docs/assets/chunks/@localSearchIndexroot.DV5SAHnQ.js +0 -1
  144. package/dist/docs/assets/guides_slack.md.zriQpU_9.lean.js +0 -1
  145. package/dist/docs/assets/storage.md.CJYd5YmN.lean.js +0 -1
  146. package/dist/playground/assets/index-CiCmH6R-.css +0 -1
  147. /package/dist/docs/assets/{deployment.md.Cdp4rTE4.lean.js → deployment.md.B8kW-h7P.lean.js} +0 -0
  148. /package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.lean.js → example-agents_benny.md.B-LIDGja.lean.js} +0 -0
  149. /package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.lean.js → example-agents_oncall.md.CBmyuAKc.lean.js} +0 -0
  150. /package/dist/docs/assets/{reference_http-api.md.DGrw_wOu.lean.js → reference_http-api.md.DMbdFGVQ.lean.js} +0 -0
  151. /package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.lean.js → troubleshooting.md.C6n3wCr6.lean.js} +0 -0
@@ -999,7 +999,7 @@ function emitSlackChannel(
999
999
  }
1000
1000
  if (socketMode) {
1001
1001
  out.setupSteps.push(
1002
- `Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack setup\`, then \`${CLI_COMMAND_NAME} slack init --channel-posts\`, and set the generated tokens.`
1002
+ `Slack channel-watch / reaction / channel-created triggers need a dedicated Socket Mode Slack app (not the Cursor connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`, then finish in the dashboard wizard.`
1003
1003
  );
1004
1004
  }
1005
1005
 
@@ -1069,7 +1069,7 @@ function emitSlackChannel(
1069
1069
  body = [
1070
1070
  "//",
1071
1071
  "// This channel needs a dedicated Socket Mode Slack app (not the Cursor",
1072
- `// connection): run \`${CLI_COMMAND_NAME} slack setup\` and \`${CLI_COMMAND_NAME} slack init\`.`,
1072
+ `// connection): run \`${CLI_COMMAND_NAME} slack create --channel-posts\`.`,
1073
1073
  "export default slackChannel({",
1074
1074
  ...engagement,
1075
1075
  "});",
@@ -59,6 +59,17 @@ export class SessionEventLog {
59
59
  return this.events.length;
60
60
  }
61
61
 
62
+ /**
63
+ * In-process residency vs recorded length. Today those are the same
64
+ * (`events` is the cache). A later change can drop the cache and keep
65
+ * `eventsTotal` as the durable count while `eventsRetained` shrinks to
66
+ * the unflushed tail.
67
+ */
68
+ memoryStats(): { eventsRetained: number; eventsTotal: number } {
69
+ const n = this.events.length;
70
+ return { eventsRetained: n, eventsTotal: n };
71
+ }
72
+
62
73
  /**
63
74
  * Assign the next index, record the event, and fan out to live
64
75
  * subscribers. Ordering is synchronous; file persistence is chained in
@@ -154,9 +165,16 @@ export class SessionEventLog {
154
165
  }
155
166
  }
156
167
 
168
+ export type EventLogMemoryStats = {
169
+ residentSessions: number;
170
+ eventsRetained: number;
171
+ eventsTotal: number;
172
+ };
173
+
157
174
  /** Lazily opens one {@link SessionEventLog} per session. */
158
175
  export class EventLogStore {
159
176
  private readonly logs = new Map<string, Promise<SessionEventLog>>();
177
+ private readonly opened = new Map<string, SessionEventLog>();
160
178
 
161
179
  constructor(
162
180
  private readonly eventFilePathFor: (sessionId: string) => string,
@@ -170,18 +188,38 @@ export class EventLogStore {
170
188
  let log = this.logs.get(sessionId);
171
189
  if (log === undefined) {
172
190
  const filePath = this.eventFilePathFor(sessionId);
173
- log = mkdir(dirname(filePath), { recursive: true }).then(() =>
174
- SessionEventLog.open({
175
- sessionId,
176
- filePath,
177
- onPersistError: (error) => this.onPersistError(sessionId, error),
178
- })
179
- );
191
+ log = mkdir(dirname(filePath), { recursive: true })
192
+ .then(() =>
193
+ SessionEventLog.open({
194
+ sessionId,
195
+ filePath,
196
+ onPersistError: (error) => this.onPersistError(sessionId, error),
197
+ })
198
+ )
199
+ .then((opened) => {
200
+ this.opened.set(sessionId, opened);
201
+ return opened;
202
+ });
180
203
  this.logs.set(sessionId, log);
181
204
  }
182
205
  return log;
183
206
  }
184
207
 
208
+ memoryStats(): EventLogMemoryStats {
209
+ let eventsRetained = 0;
210
+ let eventsTotal = 0;
211
+ for (const log of this.opened.values()) {
212
+ const stats = log.memoryStats();
213
+ eventsRetained += stats.eventsRetained;
214
+ eventsTotal += stats.eventsTotal;
215
+ }
216
+ return {
217
+ residentSessions: this.logs.size,
218
+ eventsRetained,
219
+ eventsTotal,
220
+ };
221
+ }
222
+
185
223
  async flushAll(): Promise<void> {
186
224
  await Promise.all(
187
225
  [...this.logs.values()].map((logPromise) =>
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Process heap + event-log residency for hosted engines.
3
+ *
4
+ * Pod RSS (`runner_daemon.pod.memory_usage_bytes`) cannot show a V8 heap-cap
5
+ * OOM: the cgroup limit is tens of GB while Node aborts near
6
+ * `--max-old-space-size`. The live path is `/v1/health`: the control plane
7
+ * already probes every engine and emits `cursor.agent_serve.engine.*` gauges.
8
+ * An optional in-process sampler (`AGENT_SDK_MEMORY_METRICS=1`) still logs
9
+ * and, if a Datadog key happens to be in the pod, posts directly.
10
+ *
11
+ * `event_log.events_retained` is the in-process cache size (the OOM
12
+ * suspect). `event_log.events_total` is the durable recorded count. They
13
+ * are equal today; a later consume-and-drop change should split them.
14
+ */
15
+
16
+ import { getHeapStatistics } from "node:v8";
17
+ import type { Attributes, Meter } from "@opentelemetry/api";
18
+ import type { EventLogMemoryStats } from "./event-log.js";
19
+
20
+ const DEFAULT_INTERVAL_MS = 15_000;
21
+ const DEFAULT_DD_SITE = "us5.datadoghq.com";
22
+ const DEFAULT_TIMEOUT_MS = 5_000;
23
+ const MB = 1024 * 1024;
24
+
25
+ export const MEMORY_METRIC_PREFIX = "agent_sdk.memory";
26
+ export const EVENT_LOG_METRIC_PREFIX = "agent_sdk.event_log";
27
+
28
+ export type ProcessMemorySample = {
29
+ heapUsedBytes: number;
30
+ heapTotalBytes: number;
31
+ heapLimitBytes: number;
32
+ rssBytes: number;
33
+ externalBytes: number;
34
+ };
35
+
36
+ export type DatadogMemoryMetricsConfig = {
37
+ apiKey: string;
38
+ seriesUrl: string;
39
+ timeoutMs: number;
40
+ };
41
+
42
+ export type ProcessMemoryTelemetryOptions = {
43
+ agentName: string;
44
+ getEventLogStats: () => EventLogMemoryStats;
45
+ logger?: (line: string) => void;
46
+ meter?: Meter;
47
+ env?: NodeJS.ProcessEnv;
48
+ intervalMs?: number;
49
+ now?: () => number;
50
+ memoryUsage?: () => {
51
+ heapUsed: number;
52
+ heapTotal: number;
53
+ rss: number;
54
+ external: number;
55
+ };
56
+ heapSizeLimit?: () => number;
57
+ postSeries?: (
58
+ config: DatadogMemoryMetricsConfig,
59
+ body: string
60
+ ) => Promise<void>;
61
+ };
62
+
63
+ export function shouldStartProcessMemoryTelemetry(
64
+ env: NodeJS.ProcessEnv = process.env
65
+ ): boolean {
66
+ const flag = env.AGENT_SDK_MEMORY_METRICS?.trim().toLowerCase();
67
+ return flag === "1" || flag === "true";
68
+ }
69
+
70
+ export function resolveDatadogMemoryMetricsConfig(
71
+ env: NodeJS.ProcessEnv = process.env
72
+ ): DatadogMemoryMetricsConfig | undefined {
73
+ const apiKey = env.DATADOG_API_KEY?.trim() || env.DD_API_KEY?.trim();
74
+ if (apiKey === undefined || apiKey === "") {
75
+ return undefined;
76
+ }
77
+ const site = env.DD_SITE?.trim() || DEFAULT_DD_SITE;
78
+ return {
79
+ apiKey,
80
+ seriesUrl: `https://api.${site}/api/v1/series`,
81
+ timeoutMs: DEFAULT_TIMEOUT_MS,
82
+ };
83
+ }
84
+
85
+ export function sampleProcessMemory(
86
+ args: {
87
+ memoryUsage?: ProcessMemoryTelemetryOptions["memoryUsage"];
88
+ heapSizeLimit?: ProcessMemoryTelemetryOptions["heapSizeLimit"];
89
+ } = {}
90
+ ): ProcessMemorySample {
91
+ const usage = args.memoryUsage?.() ?? process.memoryUsage();
92
+ const heapLimitBytes =
93
+ args.heapSizeLimit?.() ?? getHeapStatistics().heap_size_limit;
94
+ return {
95
+ heapUsedBytes: usage.heapUsed,
96
+ heapTotalBytes: usage.heapTotal,
97
+ heapLimitBytes,
98
+ rssBytes: usage.rss,
99
+ externalBytes: usage.external,
100
+ };
101
+ }
102
+
103
+ export type MemoryHealthFields = {
104
+ memory: ProcessMemorySample & { heapUsedRatio: number };
105
+ eventLog: EventLogMemoryStats;
106
+ };
107
+
108
+ export function heapUsedRatio(memory: ProcessMemorySample): number {
109
+ return memory.heapLimitBytes > 0
110
+ ? memory.heapUsedBytes / memory.heapLimitBytes
111
+ : 0;
112
+ }
113
+
114
+ export function sumEventLogStats(
115
+ stats: readonly EventLogMemoryStats[]
116
+ ): EventLogMemoryStats {
117
+ let residentSessions = 0;
118
+ let eventsRetained = 0;
119
+ let eventsTotal = 0;
120
+ for (const item of stats) {
121
+ residentSessions += item.residentSessions;
122
+ eventsRetained += item.eventsRetained;
123
+ eventsTotal += item.eventsTotal;
124
+ }
125
+ return { residentSessions, eventsRetained, eventsTotal };
126
+ }
127
+
128
+ /** Fields attached to `/v1/health` so the control plane can scrape them. */
129
+ export function buildMemoryHealthFields(
130
+ eventLog: EventLogMemoryStats,
131
+ sample: ProcessMemorySample = sampleProcessMemory()
132
+ ): MemoryHealthFields {
133
+ return {
134
+ memory: { ...sample, heapUsedRatio: heapUsedRatio(sample) },
135
+ eventLog,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Best-effort health payload. Sampling or event-log stats must never fail
141
+ * `/v1/health`; omit the fields instead.
142
+ */
143
+ export function collectMemoryHealthFields(args: {
144
+ eventLog: () => EventLogMemoryStats;
145
+ sample?: () => ProcessMemorySample;
146
+ }): MemoryHealthFields | Record<string, never> {
147
+ try {
148
+ return buildMemoryHealthFields(
149
+ args.eventLog(),
150
+ args.sample === undefined ? sampleProcessMemory() : args.sample()
151
+ );
152
+ } catch {
153
+ return {};
154
+ }
155
+ }
156
+
157
+ export function formatMemoryLogLine(args: {
158
+ agentName: string;
159
+ memory: ProcessMemorySample;
160
+ eventLog: EventLogMemoryStats;
161
+ }): string {
162
+ const ratio = heapUsedRatio(args.memory);
163
+ return (
164
+ `[agent-sdk:memory] agent=${args.agentName}` +
165
+ ` heap_used_mb=${mb(args.memory.heapUsedBytes)}` +
166
+ ` heap_total_mb=${mb(args.memory.heapTotalBytes)}` +
167
+ ` heap_limit_mb=${mb(args.memory.heapLimitBytes)}` +
168
+ ` heap_used_ratio=${ratio.toFixed(3)}` +
169
+ ` rss_mb=${mb(args.memory.rssBytes)}` +
170
+ ` event_log.sessions=${args.eventLog.residentSessions}` +
171
+ ` event_log.retained=${args.eventLog.eventsRetained}` +
172
+ ` event_log.total=${args.eventLog.eventsTotal}`
173
+ );
174
+ }
175
+
176
+ export function buildDatadogGaugeSeries(args: {
177
+ agentName: string;
178
+ memory: ProcessMemorySample;
179
+ eventLog: EventLogMemoryStats;
180
+ timestampSec: number;
181
+ }): Array<{
182
+ metric: string;
183
+ type: "gauge";
184
+ points: [[number, number]];
185
+ tags: string[];
186
+ }> {
187
+ const tags = [`agent_sdk.agent:${sanitizeTag(args.agentName)}`];
188
+ const heapRatio = heapUsedRatio(args.memory);
189
+ const points: Array<{ name: string; value: number }> = [
190
+ {
191
+ name: `${MEMORY_METRIC_PREFIX}.heap_used_bytes`,
192
+ value: args.memory.heapUsedBytes,
193
+ },
194
+ {
195
+ name: `${MEMORY_METRIC_PREFIX}.heap_total_bytes`,
196
+ value: args.memory.heapTotalBytes,
197
+ },
198
+ {
199
+ name: `${MEMORY_METRIC_PREFIX}.heap_limit_bytes`,
200
+ value: args.memory.heapLimitBytes,
201
+ },
202
+ { name: `${MEMORY_METRIC_PREFIX}.rss_bytes`, value: args.memory.rssBytes },
203
+ {
204
+ name: `${MEMORY_METRIC_PREFIX}.external_bytes`,
205
+ value: args.memory.externalBytes,
206
+ },
207
+ { name: `${MEMORY_METRIC_PREFIX}.heap_used_ratio`, value: heapRatio },
208
+ {
209
+ name: `${EVENT_LOG_METRIC_PREFIX}.resident_sessions`,
210
+ value: args.eventLog.residentSessions,
211
+ },
212
+ {
213
+ name: `${EVENT_LOG_METRIC_PREFIX}.events_retained`,
214
+ value: args.eventLog.eventsRetained,
215
+ },
216
+ {
217
+ name: `${EVENT_LOG_METRIC_PREFIX}.events_total`,
218
+ value: args.eventLog.eventsTotal,
219
+ },
220
+ ];
221
+ return points.map((point) => ({
222
+ metric: point.name,
223
+ type: "gauge",
224
+ points: [[args.timestampSec, point.value]],
225
+ tags,
226
+ }));
227
+ }
228
+
229
+ /** Start the sampler. Returns a stop function. */
230
+ export function startProcessMemoryTelemetry(
231
+ options: ProcessMemoryTelemetryOptions
232
+ ): () => void {
233
+ const env = options.env ?? process.env;
234
+ const logger = options.logger ?? (() => {});
235
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
236
+ const datadog = resolveDatadogMemoryMetricsConfig(env);
237
+ const attributes: Attributes = { "agent_sdk.agent": options.agentName };
238
+ let stopped = false;
239
+ let inFlight: Promise<void> | undefined;
240
+
241
+ const sample = (): void => {
242
+ if (stopped) {
243
+ return;
244
+ }
245
+ const memory = sampleProcessMemory({
246
+ memoryUsage: options.memoryUsage,
247
+ heapSizeLimit: options.heapSizeLimit,
248
+ });
249
+ const eventLog = options.getEventLogStats();
250
+ logger(
251
+ formatMemoryLogLine({ agentName: options.agentName, memory, eventLog })
252
+ );
253
+ recordOtel(options.meter, memory, eventLog, attributes);
254
+ if (datadog === undefined) {
255
+ return;
256
+ }
257
+ const body = JSON.stringify({
258
+ series: buildDatadogGaugeSeries({
259
+ agentName: options.agentName,
260
+ memory,
261
+ eventLog,
262
+ timestampSec: Math.floor((options.now?.() ?? Date.now()) / 1000),
263
+ }),
264
+ });
265
+ const post = options.postSeries ?? defaultPostSeries;
266
+ inFlight = post(datadog, body).catch((error: unknown) => {
267
+ const message = error instanceof Error ? error.message : String(error);
268
+ logger(`[agent-sdk:memory] datadog flush failed: ${message}`);
269
+ });
270
+ };
271
+
272
+ sample();
273
+ const timer = setInterval(sample, intervalMs);
274
+ timer.unref();
275
+ return () => {
276
+ stopped = true;
277
+ clearInterval(timer);
278
+ void inFlight;
279
+ };
280
+ }
281
+
282
+ function recordOtel(
283
+ meter: Meter | undefined,
284
+ memory: ProcessMemorySample,
285
+ eventLog: EventLogMemoryStats,
286
+ attributes: Attributes
287
+ ): void {
288
+ if (meter === undefined) {
289
+ return;
290
+ }
291
+ histogram(meter, `${MEMORY_METRIC_PREFIX}.heap_used_bytes`).record(
292
+ memory.heapUsedBytes,
293
+ attributes
294
+ );
295
+ histogram(meter, `${MEMORY_METRIC_PREFIX}.heap_limit_bytes`).record(
296
+ memory.heapLimitBytes,
297
+ attributes
298
+ );
299
+ histogram(meter, `${MEMORY_METRIC_PREFIX}.rss_bytes`).record(
300
+ memory.rssBytes,
301
+ attributes
302
+ );
303
+ histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.events_retained`).record(
304
+ eventLog.eventsRetained,
305
+ attributes
306
+ );
307
+ histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.events_total`).record(
308
+ eventLog.eventsTotal,
309
+ attributes
310
+ );
311
+ histogram(meter, `${EVENT_LOG_METRIC_PREFIX}.resident_sessions`).record(
312
+ eventLog.residentSessions,
313
+ attributes
314
+ );
315
+ }
316
+
317
+ type HistogramInstrument = {
318
+ record: (value: number, attributes?: Attributes) => void;
319
+ };
320
+
321
+ const histograms = new WeakMap<Meter, Map<string, HistogramInstrument>>();
322
+
323
+ function histogram(meter: Meter, name: string): HistogramInstrument {
324
+ let byName = histograms.get(meter);
325
+ if (byName === undefined) {
326
+ byName = new Map();
327
+ histograms.set(meter, byName);
328
+ }
329
+ const existing = byName.get(name);
330
+ if (existing !== undefined) {
331
+ return existing;
332
+ }
333
+ const unit = name.endsWith("_bytes") ? "By" : "{count}";
334
+ const created = meter.createHistogram(name, { unit });
335
+ byName.set(name, created);
336
+ return created;
337
+ }
338
+
339
+ async function defaultPostSeries(
340
+ config: DatadogMemoryMetricsConfig,
341
+ body: string
342
+ ): Promise<void> {
343
+ const controller = new AbortController();
344
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
345
+ try {
346
+ const response = await fetch(config.seriesUrl, {
347
+ method: "POST",
348
+ headers: {
349
+ "Content-Type": "application/json",
350
+ "DD-API-KEY": config.apiKey,
351
+ },
352
+ body,
353
+ signal: controller.signal,
354
+ });
355
+ if (!response.ok) {
356
+ throw new Error(`HTTP ${response.status}`);
357
+ }
358
+ } finally {
359
+ clearTimeout(timer);
360
+ }
361
+ }
362
+
363
+ function mb(bytes: number): string {
364
+ return (bytes / MB).toFixed(1);
365
+ }
366
+
367
+ function sanitizeTag(value: string): string {
368
+ const normalized = value
369
+ .trim()
370
+ .toLowerCase()
371
+ .replace(/[^a-z0-9_\-./:]+/g, "_");
372
+ return normalized === "" ? "unknown" : normalized;
373
+ }
@@ -135,6 +135,10 @@ import {
135
135
  playgroundDevBasePath,
136
136
  startPlaygroundDevServer,
137
137
  } from "./playground/toolchain.js";
138
+ import {
139
+ collectMemoryHealthFields,
140
+ sumEventLogStats,
141
+ } from "./process-memory-telemetry.js";
138
142
  import {
139
143
  ReminderRunner,
140
144
  ReminderValidationError,
@@ -162,6 +166,13 @@ import {
162
166
  } from "./session-engine.js";
163
167
  import { sessionUrlsForRequest } from "./session-urls.js";
164
168
 
169
+ function memoryHealthFields(engines: readonly SessionEngine[]) {
170
+ return collectMemoryHealthFields({
171
+ eventLog: () =>
172
+ sumEventLogStats(engines.map((engine) => engine.eventLogMemoryStats())),
173
+ });
174
+ }
175
+
165
176
  const MAX_BODY_BYTES = 32 * 1024 * 1024;
166
177
 
167
178
  /** `GET /v1/artifacts` limit defaults / ceiling. */
@@ -636,6 +647,7 @@ export async function startServer(
636
647
  slug,
637
648
  name: project.name,
638
649
  })),
650
+ ...memoryHealthFields(runtimes.map((runtime) => runtime.engine)),
639
651
  }),
640
652
  });
641
653
  }
@@ -1049,6 +1061,7 @@ function mountAgentRoutes(
1049
1061
  ok: true,
1050
1062
  name: project.name,
1051
1063
  version: packageVersion(),
1064
+ ...memoryHealthFields([engine]),
1052
1065
  }),
1053
1066
  });
1054
1067
 
@@ -91,7 +91,7 @@ import {
91
91
  namespacedKv,
92
92
  storageCasKv,
93
93
  } from "./durable-session-store.js";
94
- import { EventLogStore } from "./event-log.js";
94
+ import { type EventLogMemoryStats, EventLogStore } from "./event-log.js";
95
95
  import { TurnEventMapper } from "./event-mapper.js";
96
96
  import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
97
97
  import { createHostFilesApi } from "./host-files.js";
@@ -112,6 +112,10 @@ import { validateToolInput } from "./json-schema.js";
112
112
  import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
113
113
  import { McpHost } from "./mcp-host.js";
114
114
  import type { OtelCollector } from "./otel/collector.js";
115
+ import {
116
+ shouldStartProcessMemoryTelemetry,
117
+ startProcessMemoryTelemetry,
118
+ } from "./process-memory-telemetry.js";
115
119
  import {
116
120
  buildSessionContextBlock,
117
121
  SCHEDULE_CHANNEL_PREFIX,
@@ -364,6 +368,7 @@ export class SessionEngine {
364
368
  private readonly abCollector: ABCollector;
365
369
  private readonly otel: OtelCollector | undefined;
366
370
  private readonly otelHost: HostOtelSessionApi;
371
+ private readonly stopMemoryTelemetry: (() => void) | undefined;
367
372
  private reminderApi: HostContext["reminders"];
368
373
  private evalsApi: HostContext["evals"];
369
374
  private readonly kvApi: HostContext["kv"];
@@ -480,6 +485,14 @@ export class SessionEngine {
480
485
  }
481
486
  this.ledgerServing = this.buildLedgerServing(options);
482
487
  this.ledgerServing?.start();
488
+ this.stopMemoryTelemetry = shouldStartProcessMemoryTelemetry()
489
+ ? startProcessMemoryTelemetry({
490
+ agentName: options.project.name,
491
+ getEventLogStats: () => this.logs.memoryStats(),
492
+ logger: this.logger,
493
+ meter: options.otelMeter,
494
+ })
495
+ : undefined;
483
496
  }
484
497
 
485
498
  /**
@@ -3445,8 +3458,14 @@ export class SessionEngine {
3445
3458
  });
3446
3459
  }
3447
3460
 
3461
+ /** In-process event-log residency (OOM-suspect cache vs durable count). */
3462
+ eventLogMemoryStats(): EventLogMemoryStats {
3463
+ return this.logs.memoryStats();
3464
+ }
3465
+
3448
3466
  /** Drain running turns, dispatches, and pending writes, then dispose. */
3449
3467
  async close(): Promise<void> {
3468
+ this.stopMemoryTelemetry?.();
3450
3469
  this.approvals.cancelAll("deny");
3451
3470
  // Ledger deliveries spawn turns, so settle them before activeTurns.
3452
3471
  await this.ledgerServing?.close();