@gajae-code/coding-agent 0.12.0 → 0.12.1

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 (223) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +54 -0
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
  16. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  17. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  18. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  19. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  21. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  22. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  23. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  24. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  25. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  26. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  27. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  28. package/dist/types/reminders/star-reminder.d.ts +3 -13
  29. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  30. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  31. package/dist/types/sdk/broker/transport.d.ts +2 -0
  32. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  33. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  34. package/dist/types/sdk/bus/config.d.ts +6 -2
  35. package/dist/types/sdk/bus/index.d.ts +29 -0
  36. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  37. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  38. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  39. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  40. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  41. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  42. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  43. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  44. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  45. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  46. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  47. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  48. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  49. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  50. package/dist/types/sdk/prompt-status.d.ts +23 -3
  51. package/dist/types/sdk/session.d.ts +6 -0
  52. package/dist/types/session/agent-session.d.ts +38 -2
  53. package/dist/types/session/artifacts.d.ts +8 -0
  54. package/dist/types/session/blob-store.d.ts +57 -4
  55. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  56. package/dist/types/session/session-manager.d.ts +23 -6
  57. package/dist/types/session/session-storage.d.ts +4 -0
  58. package/dist/types/session/streaming-output.d.ts +36 -7
  59. package/dist/types/setup/credential-import.d.ts +12 -0
  60. package/dist/types/skill-state/active-state.d.ts +12 -0
  61. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  62. package/dist/types/task/executor.d.ts +3 -2
  63. package/dist/types/task/index.d.ts +3 -1
  64. package/dist/types/task/provider-retry-status.d.ts +8 -0
  65. package/dist/types/task/receipt.d.ts +7 -1
  66. package/dist/types/task/types.d.ts +23 -0
  67. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  68. package/dist/types/tools/bash-interactive.d.ts +4 -1
  69. package/dist/types/tools/bash.d.ts +3 -1
  70. package/dist/types/tools/image-gen.d.ts +4 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/output-meta.d.ts +23 -2
  73. package/dist/types/tools/path-utils.d.ts +1 -0
  74. package/dist/types/tools/read.d.ts +0 -6
  75. package/dist/types/tools/review.d.ts +5 -7
  76. package/dist/types/tools/subagent.d.ts +2 -0
  77. package/dist/types/utils/gh.d.ts +13 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  80. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  81. package/dist/types/web/search/providers/xai.d.ts +2 -0
  82. package/package.json +10 -8
  83. package/scripts/acp-conformance-agent.ts +218 -0
  84. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  85. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  86. package/scripts/resident-memory-bench.ts +1341 -0
  87. package/scripts/run-acp-conformance.ts +285 -0
  88. package/src/async/job-manager.ts +78 -20
  89. package/src/cli/fast-help.ts +1 -1
  90. package/src/cli/plugin-cli.ts +176 -18
  91. package/src/commands/team.ts +19 -1
  92. package/src/config/file-lock.ts +11 -2
  93. package/src/config/settings-schema.ts +51 -0
  94. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  95. package/src/coordinator-mcp/server.ts +5 -12
  96. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  97. package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
  98. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  99. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
  100. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  101. package/src/defaults/gjc-defaults.ts +4 -4
  102. package/src/exa/mcp-client.ts +12 -3
  103. package/src/exec/bash-executor.ts +144 -27
  104. package/src/extensibility/extensions/runner.ts +15 -0
  105. package/src/extensibility/extensions/types.ts +18 -1
  106. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  107. package/src/extensibility/gjc-plugins/index.ts +19 -2
  108. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  109. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  110. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  111. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  112. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  113. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  114. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  115. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  116. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  117. package/src/extensibility/gjc-plugins/types.ts +141 -0
  118. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  119. package/src/gjc-runtime/launch-tmux.ts +25 -6
  120. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  121. package/src/gjc-runtime/ralplan-runtime.ts +411 -17
  122. package/src/gjc-runtime/team-launch.ts +8 -0
  123. package/src/gjc-runtime/team-runtime.ts +140 -32
  124. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  127. package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
  128. package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
  129. package/src/gjc-runtime/workflow-manifest.ts +4 -68
  130. package/src/internal-urls/docs-index.generated.ts +15 -15
  131. package/src/modes/DESIGN.md +51 -0
  132. package/src/modes/acp/acp-agent.ts +298 -186
  133. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  134. package/src/modes/components/notifications-settings-editor.ts +12 -1
  135. package/src/modes/components/oauth-selector.ts +68 -30
  136. package/src/modes/components/session-observer-overlay.ts +305 -86
  137. package/src/modes/components/settings-selector.ts +56 -5
  138. package/src/modes/components/status-line/gh.ts +16 -0
  139. package/src/modes/components/tool-status-header.ts +3 -14
  140. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  141. package/src/modes/controllers/command-controller.ts +4 -0
  142. package/src/modes/controllers/extension-ui-controller.ts +7 -0
  143. package/src/modes/controllers/selector-controller.ts +45 -0
  144. package/src/modes/runtime-init.ts +3 -0
  145. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  146. package/src/prompts/system/eager-todo.md +8 -4
  147. package/src/prompts/system/system-prompt.md +11 -4
  148. package/src/prompts/tools/bash.md +5 -5
  149. package/src/prompts/tools/search-tool-bm25.md +5 -0
  150. package/src/prompts/tools/task.md +1 -1
  151. package/src/reminders/star-reminder.ts +4 -42
  152. package/src/runtime-mcp/manager.ts +19 -4
  153. package/src/sdk/acp/adapter.ts +22 -6
  154. package/src/sdk/broker/transport.ts +35 -3
  155. package/src/sdk/bus/attachment-registry.ts +5 -1
  156. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  157. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  158. package/src/sdk/bus/config.ts +18 -2
  159. package/src/sdk/bus/index.ts +661 -126
  160. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  161. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  162. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  163. package/src/sdk/bus/reconciliation-store.ts +95 -16
  164. package/src/sdk/bus/rich-draft.ts +1 -1
  165. package/src/sdk/bus/rich-render.ts +2 -2
  166. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  167. package/src/sdk/bus/telegram-cli.ts +33 -8
  168. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  169. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  170. package/src/sdk/bus/telegram-daemon.ts +916 -23
  171. package/src/sdk/bus/telegram-reference.ts +40 -3
  172. package/src/sdk/bus/threaded-render.ts +48 -3
  173. package/src/sdk/bus/topic-registry.ts +20 -0
  174. package/src/sdk/host/query/revision-store.ts +36 -2
  175. package/src/sdk/host/reverse-leases.ts +44 -7
  176. package/src/sdk/prompt-status.ts +16 -3
  177. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  178. package/src/sdk/session.ts +88 -9
  179. package/src/session/agent-session.ts +822 -172
  180. package/src/session/artifacts.ts +22 -0
  181. package/src/session/auth-broker-config.ts +10 -3
  182. package/src/session/blob-store.ts +915 -24
  183. package/src/session/internal/managed-session-scope.ts +44 -6
  184. package/src/session/internal/managed-session-storage.ts +48 -6
  185. package/src/session/session-manager.ts +1614 -435
  186. package/src/session/session-storage.ts +9 -1
  187. package/src/session/streaming-output.ts +238 -68
  188. package/src/setup/credential-import.ts +75 -13
  189. package/src/skill-state/active-state.ts +32 -0
  190. package/src/skill-state/workflow-hud.ts +17 -0
  191. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  192. package/src/task/executor.ts +122 -39
  193. package/src/task/index.ts +23 -14
  194. package/src/task/provider-retry-status.ts +16 -0
  195. package/src/task/receipt.ts +23 -7
  196. package/src/task/render.ts +6 -2
  197. package/src/task/types.ts +108 -0
  198. package/src/tools/ask-answer-registry.ts +12 -6
  199. package/src/tools/ask.ts +86 -55
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash-interactive.ts +8 -3
  203. package/src/tools/bash.ts +503 -60
  204. package/src/tools/find.ts +1 -0
  205. package/src/tools/image-gen.ts +29 -5
  206. package/src/tools/index.ts +4 -0
  207. package/src/tools/output-meta.ts +108 -14
  208. package/src/tools/path-utils.ts +3 -2
  209. package/src/tools/read.ts +18 -0
  210. package/src/tools/review.ts +5 -7
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/subagent-render.ts +6 -2
  213. package/src/tools/subagent.ts +6 -0
  214. package/src/tools/telegram-send.ts +6 -3
  215. package/src/utils/gh.ts +39 -0
  216. package/src/utils/image-resize.ts +3 -0
  217. package/src/web/search/providers/anthropic.ts +4 -4
  218. package/src/web/search/providers/kimi.ts +15 -5
  219. package/src/web/search/providers/searxng.ts +47 -4
  220. package/src/web/search/providers/xai.ts +7 -2
  221. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  222. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  223. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -0,0 +1,1341 @@
1
+ /**
2
+ * Pinned, child-isolated resident-cache benchmark.
3
+ *
4
+ * Each `--runs` repetition executes in a fresh Bun child. The default fixture is
5
+ * 5,000 deterministic, unique 48 KiB messages; use the small overrides only for
6
+ * local smoke checks, not performance comparisons.
7
+ *
8
+ * Normal measurements (five child runs per command):
9
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode rss --runs 5
10
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode put-latency --runs 5
11
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode read-churn --runs 5
12
+ *
13
+ * HEAD forced-rebuild baseline (copy this script to the pinned HEAD worktree):
14
+ * git worktree add /tmp/gjc-bench-head 3649db42e
15
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode read-churn --baseline forced-rebuild --runs 5
16
+ *
17
+ * Small smoke fixture and deliberate invalid-run demonstration:
18
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode rss --entries 8 --bytes-per-entry 4096 --runs 1
19
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode put-latency --puts 64 --runs 1
20
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode read-churn --entries 8 --bytes-per-entry 4096 --cache-cap-bytes 1024 --runs 1
21
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode read-churn --entries 8 --bytes-per-entry 4096 --cache-cap-bytes 1024 --skip-gc --runs 1
22
+ * TMPDIR="$HOME/tmp-gjc-tests/" GJC_CODING_AGENT_DIR="$(mktemp -d)" NO_COLOR=1 bun packages/coding-agent/scripts/resident-memory-bench.ts --mode rss --force-memory-only --runs 1
23
+ *
24
+ * `--skip-gc` deliberately bypasses the required turn boundary and forced GC;
25
+ * the --skip-gc command must exit non-zero with the invalid-run diagnostic. Do not
26
+ * use it for measurements. `--baseline` is an alias for `--baseline forced-rebuild`.
27
+ * `--force-memory-only` is bench-only: it poisons the isolated cache root to
28
+ * exercise the existing fallback and is not a supported product configuration.
29
+ * RSS mode reports append-phase and fresh-process reopen measurements with the full
30
+ * `process.memoryUsage()` breakdown, plus repeated forced-GC idle-turn reclaim samples.
31
+ * AC-1 passes only when append steady-state RSS delta stays within 100 MiB; a
32
+ * post-reclaim result within that limit is separately labeled documented evidence. AC-2
33
+ * measures direct `putSync` calls over pre-generated unique Buffers into a
34
+ * canonical MemoryBlobStore and an adopted verified EphemeralBlobStore.
35
+ * SessionManager append figures are separate `e2eAppend` diagnostics. A direct
36
+ * store ratio above 1.5x is documented evidence, not a relaxation of the
37
+ * secure O_EXCL|O_NOFOLLOW, owner-only, lazy-EEXIST verification contract.
38
+ */
39
+
40
+ import type * as nodeFs from "node:fs";
41
+ import * as fs from "node:fs/promises";
42
+ import { createRequire, syncBuiltinESMExports } from "node:module";
43
+ import * as os from "node:os";
44
+ import * as path from "node:path";
45
+ import { EphemeralBlobStore, MemoryBlobStore, openVerifiedResidentCacheInstanceDir } from "../src/session/blob-store";
46
+ import type {
47
+ SessionManagerObservabilityStats,
48
+ SessionManager as SessionManagerType,
49
+ } from "../src/session/session-manager";
50
+ import * as sessionManagerModule from "../src/session/session-manager";
51
+
52
+ const { SessionManager } = sessionManagerModule;
53
+
54
+ const SCHEMA_VERSION = 4;
55
+ const SYNTHETIC_SEED = 0x5eedc0de;
56
+ const DEFAULT_RUNS = 5;
57
+ const RSS_ENTRY_COUNT = 5_000;
58
+ const RSS_BYTES_PER_ENTRY = 48 * 1024;
59
+ const READ_CHURN_CYCLES = 100;
60
+ const PUT_WARMUP_ITERATIONS = 1_000;
61
+ const PUT_MEASURE_ITERATIONS = 10_000;
62
+ const PUT_BYTES = 4 * 1024;
63
+ const BASELINE_MARKER_BYTES = 128;
64
+ const AC1_APPEND_PHASE_RSS_LIMIT_BYTES = 100 * 1024 * 1024;
65
+ const MEMORY_RECLAIM_GC_ROUNDS = 4;
66
+ const SYNTHETIC_TEXT_PATTERN =
67
+ "gjc-resident-cache-fixture-abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789";
68
+ const AC2_DIRECT_STORE_MEDIAN_RATIO_LIMIT = 1.5;
69
+
70
+ type Mode = "rss" | "put-latency" | "read-churn";
71
+ type RetentionMode = "above-cap" | "below-cap";
72
+
73
+ type CliArgs = {
74
+ mode: Mode;
75
+ runs: number;
76
+ worker: boolean;
77
+ baseline: boolean;
78
+ retention: RetentionMode;
79
+ entries?: number;
80
+ bytesPerEntry?: number;
81
+ cacheCapBytes?: number;
82
+ puts?: number;
83
+ skipGc: boolean;
84
+ forceMemoryOnly: boolean;
85
+ freshOpenSessionFile?: string;
86
+ };
87
+
88
+ type NumericSummary = {
89
+ min: number;
90
+ p25: number;
91
+ median: number;
92
+ p75: number;
93
+ iqr: number;
94
+ max: number;
95
+ };
96
+
97
+ type RunMetadata = {
98
+ bunVersion: string;
99
+ platform: string;
100
+ arch: string;
101
+ cpu: string | null;
102
+ };
103
+
104
+ type FixtureDimensions = {
105
+ seed: number;
106
+ entries: number;
107
+ bytesPerEntry: number;
108
+ };
109
+
110
+ type CacheBackingObservability = {
111
+ materializedCacheDemotedCount?: number;
112
+ residentCacheAdoptFallbackCount?: number;
113
+ residentCacheTrustRejectCount?: number;
114
+ residentCacheWin32FallbackCount?: number;
115
+ };
116
+
117
+ type MemorySample = {
118
+ rssBytes: number;
119
+ heapTotalBytes: number;
120
+ heapUsedBytes: number;
121
+ externalBytes: number;
122
+ arrayBuffersBytes: number;
123
+ /** Lifetime maximum RSS from getrusage; null where Bun does not expose it. */
124
+ rusageMaxRssBytes: number | null;
125
+ };
126
+
127
+ type MemoryDelta = {
128
+ rssBytes: number;
129
+ heapTotalBytes: number;
130
+ heapUsedBytes: number;
131
+ externalBytes: number;
132
+ arrayBuffersBytes: number;
133
+ };
134
+
135
+ type MemoryPressureReclaim = {
136
+ method: "forced-gc-with-idle-turns";
137
+ gcRounds: number;
138
+ };
139
+
140
+ type ResidentStoreMode = "disk-preferred" | "forced-memory-fallback";
141
+
142
+ type RssAppendPhase = {
143
+ baseline: MemorySample;
144
+ postAppend: MemorySample;
145
+ postFlush: MemorySample;
146
+ steadyState: MemorySample;
147
+ postReclaim: MemorySample;
148
+ postRead: MemorySample;
149
+ postReadGc: MemorySample;
150
+ steadyStateDelta: MemoryDelta;
151
+ postReclaimDelta: MemoryDelta;
152
+ postReadGcDelta: MemoryDelta;
153
+ reclaim: MemoryPressureReclaim;
154
+ reclaimedBytes: number;
155
+ reclaimRssDeltaBytes: number;
156
+ observability: CacheBackingObservability;
157
+ residentCacheDiskBytes: number | null;
158
+ residentStoreMode: ResidentStoreMode;
159
+ guard: number;
160
+ };
161
+
162
+ type RssFreshOpenPhase = {
163
+ baseline: MemorySample;
164
+ postOpen: MemorySample;
165
+ steadyState: MemorySample;
166
+ postReclaim: MemorySample;
167
+ steadyStateDelta: MemoryDelta;
168
+ postReclaimDelta: MemoryDelta;
169
+ reclaim: MemoryPressureReclaim;
170
+ reclaimedBytes: number;
171
+ reclaimRssDeltaBytes: number;
172
+ observability: CacheBackingObservability;
173
+ residentCacheDiskBytes: number | null;
174
+ residentStoreMode: ResidentStoreMode;
175
+ };
176
+
177
+ type RssWorkerResult = {
178
+ schemaVersion: number;
179
+ mode: "rss";
180
+ metadata: RunMetadata;
181
+ fixture: FixtureDimensions;
182
+ appendPhase: RssAppendPhase;
183
+ freshOpenPhase: RssFreshOpenPhase;
184
+ };
185
+
186
+ type FreshOpenRssWorkerResult = {
187
+ schemaVersion: number;
188
+ mode: "rss";
189
+ phase: "fresh-open";
190
+ metadata: RunMetadata;
191
+ fixture: FixtureDimensions;
192
+ freshOpenPhase: RssFreshOpenPhase;
193
+ };
194
+
195
+ type DirectStorePutMetrics = {
196
+ canonicalMemoryPutMs: NumericSummary;
197
+ adoptedEphemeralPutMs: NumericSummary;
198
+ adoptedEphemeralToCanonicalMemoryMedianRatio: number;
199
+ adoptedEphemeralPutFsyncCalls: number;
200
+ };
201
+
202
+ type E2eAppendMetrics = {
203
+ memoryAppendMs: NumericSummary;
204
+ residentAppendMs: NumericSummary;
205
+ residentToMemoryAppendMedianRatio: number;
206
+ memoryWallMs: number;
207
+ residentWallMs: number;
208
+ memoryEntriesPerSecond: number;
209
+ residentEntriesPerSecond: number;
210
+ residentToMemoryThroughputRatio: number;
211
+ residentAppendFsyncCalls: number;
212
+ };
213
+
214
+ type PutLatencyWorkerResult = {
215
+ schemaVersion: number;
216
+ mode: "put-latency";
217
+ metadata: RunMetadata;
218
+ fixture: FixtureDimensions & { warmupIterations: number; measureIterations: number };
219
+ metrics: {
220
+ directStorePut: DirectStorePutMetrics;
221
+ e2eAppend: E2eAppendMetrics;
222
+ };
223
+ guard: number;
224
+ };
225
+
226
+ type ReadChurnCycle = {
227
+ cycle: number;
228
+ wallMs: number;
229
+ cpuMicros: number;
230
+ materializedEntriesCachePopulateDelta: number;
231
+ pathOnlyContextBuildDelta: number;
232
+ };
233
+
234
+ type ReadChurnWorkerResult = {
235
+ schemaVersion: number;
236
+ mode: "read-churn";
237
+ metadata: RunMetadata;
238
+ fixture: FixtureDimensions & {
239
+ cycles: number;
240
+ retention: RetentionMode;
241
+ cacheCapBytes: number | "production-default";
242
+ };
243
+ baseline: "none" | "forced-rebuild";
244
+ cycles: ReadChurnCycle[];
245
+ metrics: {
246
+ cycleWallMs: NumericSummary;
247
+ cycleCpuMicros: NumericSummary;
248
+ materializedEntriesCachePopulateDelta: NumericSummary;
249
+ pathOnlyContextBuildDelta: NumericSummary;
250
+ aggregateWallMs: number;
251
+ aggregateCpuMicros: number;
252
+ aggregateMaterializedEntriesCachePopulateDelta: number;
253
+ aggregatePathOnlyContextBuildDelta: number;
254
+ };
255
+ guard: number;
256
+ };
257
+
258
+ type WorkerResult = RssWorkerResult | PutLatencyWorkerResult | ReadChurnWorkerResult;
259
+
260
+ type TestHooks = { materializedCacheMaxBytesOverride?: number };
261
+ type SessionManagerModuleWithTestHooks = typeof sessionManagerModule & { SessionManagerTestHooks?: TestHooks };
262
+
263
+ function usage(): never {
264
+ throw new Error(
265
+ "Usage: bun packages/coding-agent/scripts/resident-memory-bench.ts --mode rss|put-latency|read-churn [--runs N] [--baseline [forced-rebuild]] [--entries N] [--bytes-per-entry N] [--cache-cap-bytes N] [--puts N] [--retention above-cap|below-cap] [--skip-gc] [--force-memory-only] [--fresh-open-session-file PATH]",
266
+ );
267
+ }
268
+
269
+ function parseNonNegativeInteger(value: string | undefined, flag: string): number {
270
+ if (!value || !/^\d+$/.test(value)) throw new Error(`${flag} requires a non-negative integer.`);
271
+ const parsed = Number(value);
272
+ if (!Number.isSafeInteger(parsed)) throw new Error(`${flag} must be a safe integer.`);
273
+ return parsed;
274
+ }
275
+
276
+ function parsePositiveInteger(value: string | undefined, flag: string): number {
277
+ const parsed = parseNonNegativeInteger(value, flag);
278
+ if (parsed === 0) throw new Error(`${flag} must be greater than zero.`);
279
+ return parsed;
280
+ }
281
+
282
+ function parseArgs(argv: string[]): CliArgs {
283
+ const args: CliArgs = {
284
+ mode: "rss",
285
+ runs: DEFAULT_RUNS,
286
+ worker: false,
287
+ baseline: false,
288
+ retention: "above-cap",
289
+ skipGc: false,
290
+ forceMemoryOnly: false,
291
+ };
292
+ for (let index = 0; index < argv.length; index++) {
293
+ const arg = argv[index];
294
+ switch (arg) {
295
+ case "--mode": {
296
+ const mode = argv[++index];
297
+ if (mode !== "rss" && mode !== "put-latency" && mode !== "read-churn") usage();
298
+ args.mode = mode;
299
+ break;
300
+ }
301
+ case "--runs":
302
+ args.runs = parsePositiveInteger(argv[++index], "--runs");
303
+ break;
304
+ case "--entries":
305
+ args.entries = parsePositiveInteger(argv[++index], "--entries");
306
+ break;
307
+ case "--bytes-per-entry":
308
+ args.bytesPerEntry = parsePositiveInteger(argv[++index], "--bytes-per-entry");
309
+ break;
310
+ case "--cache-cap-bytes":
311
+ args.cacheCapBytes = parseNonNegativeInteger(argv[++index], "--cache-cap-bytes");
312
+ break;
313
+ case "--puts":
314
+ args.puts = parsePositiveInteger(argv[++index], "--puts");
315
+ break;
316
+ case "--retention": {
317
+ const retention = argv[++index];
318
+ if (retention !== "above-cap" && retention !== "below-cap") usage();
319
+ args.retention = retention;
320
+ break;
321
+ }
322
+ case "--baseline":
323
+ args.baseline = true;
324
+ if (argv[index + 1] === "forced-rebuild") index++;
325
+ break;
326
+ case "--worker":
327
+ args.worker = true;
328
+ break;
329
+ case "--skip-gc":
330
+ args.skipGc = true;
331
+ break;
332
+ case "--force-memory-only":
333
+ args.forceMemoryOnly = true;
334
+ break;
335
+ case "--fresh-open-session-file": {
336
+ const sessionFile = argv[++index];
337
+ if (!sessionFile) throw new Error("--fresh-open-session-file requires a path.");
338
+ args.freshOpenSessionFile = sessionFile;
339
+ break;
340
+ }
341
+
342
+ default:
343
+ usage();
344
+ }
345
+ }
346
+ if (args.baseline && args.mode !== "read-churn") throw new Error("--baseline is only valid with --mode read-churn.");
347
+ if (args.skipGc && args.mode !== "read-churn") throw new Error("--skip-gc is only valid with --mode read-churn.");
348
+ if (args.freshOpenSessionFile && (!args.worker || args.mode !== "rss"))
349
+ throw new Error("--fresh-open-session-file is an internal RSS worker flag.");
350
+ if (args.forceMemoryOnly && args.mode !== "rss")
351
+ throw new Error("--force-memory-only is only valid with --mode rss.");
352
+ return args;
353
+ }
354
+
355
+ function fixtureFor(args: CliArgs): FixtureDimensions {
356
+ return {
357
+ seed: SYNTHETIC_SEED,
358
+ entries: args.entries ?? RSS_ENTRY_COUNT,
359
+ bytesPerEntry: args.bytesPerEntry ?? RSS_BYTES_PER_ENTRY,
360
+ };
361
+ }
362
+
363
+ function fixtureForMode(args: CliArgs): FixtureDimensions {
364
+ if (args.mode === "put-latency") {
365
+ return {
366
+ seed: SYNTHETIC_SEED,
367
+ entries: args.puts ?? PUT_MEASURE_ITERATIONS,
368
+ bytesPerEntry: PUT_BYTES,
369
+ };
370
+ }
371
+ return fixtureFor(args);
372
+ }
373
+
374
+ function metadata(): RunMetadata {
375
+ return {
376
+ bunVersion: Bun.version,
377
+ platform: process.platform,
378
+ arch: process.arch,
379
+ cpu: os.cpus()[0]?.model ?? null,
380
+ };
381
+ }
382
+
383
+ function percentile(sorted: readonly number[], percentileValue: number): number {
384
+ if (sorted.length === 0) return 0;
385
+ const index = Math.min(sorted.length - 1, Math.ceil((percentileValue / 100) * sorted.length) - 1);
386
+ return sorted[index] ?? 0;
387
+ }
388
+
389
+ function summarize(samples: readonly number[]): NumericSummary {
390
+ const sorted = [...samples].sort((left, right) => left - right);
391
+ const p25 = percentile(sorted, 25);
392
+ const p75 = percentile(sorted, 75);
393
+ return {
394
+ min: sorted[0] ?? 0,
395
+ p25,
396
+ median: percentile(sorted, 50),
397
+ p75,
398
+ iqr: p75 - p25,
399
+ max: sorted.at(-1) ?? 0,
400
+ };
401
+ }
402
+
403
+ function sum(values: readonly number[]): number {
404
+ return values.reduce((total, value) => total + value, 0);
405
+ }
406
+
407
+ function rusageMaxRssBytes(): number | null {
408
+ if (typeof process.resourceUsage !== "function") return null;
409
+ const maxRss = process.resourceUsage().maxRSS;
410
+ return Number.isFinite(maxRss) ? maxRss : null;
411
+ }
412
+
413
+ function memorySample(): MemorySample {
414
+ const memory = process.memoryUsage();
415
+ return {
416
+ rssBytes: memory.rss,
417
+ heapTotalBytes: memory.heapTotal,
418
+ heapUsedBytes: memory.heapUsed,
419
+ externalBytes: memory.external,
420
+ arrayBuffersBytes: memory.arrayBuffers,
421
+ rusageMaxRssBytes: rusageMaxRssBytes(),
422
+ };
423
+ }
424
+
425
+ function subtractMemorySamples(after: MemorySample, before: MemorySample): MemoryDelta {
426
+ return {
427
+ rssBytes: after.rssBytes - before.rssBytes,
428
+ heapTotalBytes: after.heapTotalBytes - before.heapTotalBytes,
429
+ heapUsedBytes: after.heapUsedBytes - before.heapUsedBytes,
430
+ externalBytes: after.externalBytes - before.externalBytes,
431
+ arrayBuffersBytes: after.arrayBuffersBytes - before.arrayBuffersBytes,
432
+ };
433
+ }
434
+
435
+ function summarizeMemorySamples(samples: readonly MemorySample[]): {
436
+ rssBytes: NumericSummary;
437
+ heapTotalBytes: NumericSummary;
438
+ heapUsedBytes: NumericSummary;
439
+ externalBytes: NumericSummary;
440
+ arrayBuffersBytes: NumericSummary;
441
+ rusageMaxRssBytes: NumericSummary | null;
442
+ } {
443
+ const rusageMaxRssSamples = samples
444
+ .map(sample => sample.rusageMaxRssBytes)
445
+ .filter((sample): sample is number => sample !== null);
446
+ return {
447
+ rssBytes: summarize(samples.map(sample => sample.rssBytes)),
448
+ heapTotalBytes: summarize(samples.map(sample => sample.heapTotalBytes)),
449
+ heapUsedBytes: summarize(samples.map(sample => sample.heapUsedBytes)),
450
+ externalBytes: summarize(samples.map(sample => sample.externalBytes)),
451
+ arrayBuffersBytes: summarize(samples.map(sample => sample.arrayBuffersBytes)),
452
+ rusageMaxRssBytes: rusageMaxRssSamples.length === samples.length ? summarize(rusageMaxRssSamples) : null,
453
+ };
454
+ }
455
+
456
+ function summarizeMemoryDeltas(samples: readonly MemoryDelta[]): {
457
+ rssBytes: NumericSummary;
458
+ heapTotalBytes: NumericSummary;
459
+ heapUsedBytes: NumericSummary;
460
+ externalBytes: NumericSummary;
461
+ arrayBuffersBytes: NumericSummary;
462
+ } {
463
+ return {
464
+ rssBytes: summarize(samples.map(sample => sample.rssBytes)),
465
+ heapTotalBytes: summarize(samples.map(sample => sample.heapTotalBytes)),
466
+ heapUsedBytes: summarize(samples.map(sample => sample.heapUsedBytes)),
467
+ externalBytes: summarize(samples.map(sample => sample.externalBytes)),
468
+ arrayBuffersBytes: summarize(samples.map(sample => sample.arrayBuffersBytes)),
469
+ };
470
+ }
471
+
472
+ function cacheBackingObservability(stats: SessionManagerObservabilityStats): CacheBackingObservability {
473
+ const values = stats as unknown as Record<string, unknown>;
474
+ const numberAt = (key: string): number | undefined => {
475
+ const value = values[key];
476
+ return typeof value === "number" ? value : undefined;
477
+ };
478
+ return {
479
+ materializedCacheDemotedCount: numberAt("materializedCacheDemotedCount"),
480
+ residentCacheAdoptFallbackCount: numberAt("residentCacheAdoptFallbackCount"),
481
+ residentCacheTrustRejectCount: numberAt("residentCacheTrustRejectCount"),
482
+ residentCacheWin32FallbackCount: numberAt("residentCacheWin32FallbackCount"),
483
+ };
484
+ }
485
+
486
+ function syntheticBuffer(seed: number, index: number, bytes: number): Buffer {
487
+ const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
488
+ let state = (seed ^ Math.imul(index + 1, 0x9e3779b9)) >>> 0;
489
+ const next = (): number => {
490
+ state ^= state << 13;
491
+ state ^= state >>> 17;
492
+ state ^= state << 5;
493
+ return state >>> 0;
494
+ };
495
+ const data = Buffer.allocUnsafe(bytes);
496
+ const prefix = `entry-${index.toString(36)}:`;
497
+ data.write(prefix, 0, "utf8");
498
+ for (let offset = prefix.length; offset < data.length; offset++) {
499
+ data[offset] = alphabet.charCodeAt(next() % alphabet.length);
500
+ }
501
+ return data;
502
+ }
503
+
504
+ /** Build an exact-size ASCII fixture without allocating a throwaway Buffer before the put path. */
505
+ function syntheticText(seed: number, index: number, bytes: number): string {
506
+ const prefix = `entry-${seed.toString(36)}-${index.toString(36)}:`;
507
+ if (bytes <= prefix.length) return prefix.slice(0, bytes);
508
+ const offset = (seed ^ index) >>> 0;
509
+ const pattern = `${SYNTHETIC_TEXT_PATTERN.slice(offset % SYNTHETIC_TEXT_PATTERN.length)}${SYNTHETIC_TEXT_PATTERN.slice(0, offset % SYNTHETIC_TEXT_PATTERN.length)}`;
510
+ const bodyBytes = bytes - prefix.length;
511
+ return `${prefix}${pattern.repeat(Math.ceil(bodyBytes / pattern.length)).slice(0, bodyBytes)}`;
512
+ }
513
+
514
+ function appendSyntheticEntries(manager: SessionManagerType, fixture: FixtureDimensions): void {
515
+ for (let index = 0; index < fixture.entries; index++) {
516
+ manager.appendMessage({
517
+ role: "user",
518
+ content: syntheticText(fixture.seed, index, fixture.bytesPerEntry),
519
+ timestamp: index,
520
+ });
521
+ }
522
+ }
523
+
524
+ async function withPersistentFixture<T>(
525
+ fixture: FixtureDimensions,
526
+ forceMemoryOnly: boolean,
527
+ operation: (manager: SessionManagerType) => Promise<T>,
528
+ ): Promise<T> {
529
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-resident-memory-bench-"));
530
+ let manager: SessionManagerType | undefined;
531
+ try {
532
+ if (forceMemoryOnly) await forceResidentCacheMemoryFallback();
533
+ manager = SessionManager.create(root, path.join(root, "sessions"));
534
+ appendSyntheticEntries(manager, fixture);
535
+ return await operation(manager);
536
+ } finally {
537
+ if (manager) await manager.close();
538
+ await fs.rm(root, { recursive: true, force: true });
539
+ }
540
+ }
541
+
542
+ async function forceResidentCacheMemoryFallback(): Promise<void> {
543
+ const agentDir = process.env.GJC_CODING_AGENT_DIR;
544
+ if (!agentDir) throw new Error("--force-memory-only requires GJC_CODING_AGENT_DIR to be set.");
545
+ await fs.mkdir(agentDir, { recursive: true, mode: 0o700 });
546
+ await fs.writeFile(path.join(agentDir, "resident-cache"), "forced resident cache memory fallback\n", {
547
+ encoding: "utf8",
548
+ flag: "wx",
549
+ mode: 0o600,
550
+ });
551
+ }
552
+
553
+ async function residentCacheDiskBytes(): Promise<number | null> {
554
+ const agentDir = process.env.GJC_CODING_AGENT_DIR;
555
+ if (!agentDir) return null;
556
+ const root = path.join(agentDir, "resident-cache");
557
+ const instanceDirs = await fs.readdir(root, { withFileTypes: true }).catch(() => undefined);
558
+ if (!instanceDirs) return null;
559
+ const directoryBytes = async (directory: string): Promise<number> => {
560
+ let bytes = 0;
561
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
562
+ const pathname = path.join(directory, entry.name);
563
+ if (entry.isDirectory()) bytes += await directoryBytes(pathname);
564
+ else if (entry.isFile()) bytes += (await fs.stat(pathname)).size;
565
+ }
566
+ return bytes;
567
+ };
568
+ return await Promise.all(
569
+ instanceDirs
570
+ .filter(entry => entry.isDirectory() && entry.name.startsWith("i-"))
571
+ .map(entry => directoryBytes(path.join(root, entry.name))),
572
+ ).then(sum);
573
+ }
574
+
575
+ function readPair(manager: SessionManagerType): number {
576
+ const entries = manager.getEntries();
577
+ const context = manager.buildSessionContext();
578
+ return entries.length + context.messages.length;
579
+ }
580
+
581
+ async function settleAndForceGc(): Promise<void> {
582
+ await Bun.sleep(0);
583
+ Bun.gc(true);
584
+ }
585
+
586
+ async function settleAndForceGcTwice(): Promise<void> {
587
+ await settleAndForceGc();
588
+ await settleAndForceGc();
589
+ }
590
+
591
+ /**
592
+ * Let the collector and allocator reclaim after an idle turn without injecting a
593
+ * larger temporary allocation that itself becomes an RSS high-water artifact.
594
+ */
595
+ async function reclaimAfterMemoryPressure(): Promise<MemoryPressureReclaim> {
596
+ for (let round = 0; round < MEMORY_RECLAIM_GC_ROUNDS; round++) {
597
+ Bun.gc(true);
598
+ await Bun.sleep(round === MEMORY_RECLAIM_GC_ROUNDS - 1 ? 50 : 0);
599
+ }
600
+ return { method: "forced-gc-with-idle-turns", gcRounds: MEMORY_RECLAIM_GC_ROUNDS };
601
+ }
602
+
603
+ /** Allow completed SessionManager.open async frames to become collectible before sampling. */
604
+ async function settleFreshOpenRss(): Promise<void> {
605
+ await settleAndForceGcTwice();
606
+ await Bun.sleep(50);
607
+ await settleAndForceGcTwice();
608
+ }
609
+
610
+ function cacheCapOverride(args: CliArgs): number | undefined {
611
+ if (args.cacheCapBytes !== undefined) return args.cacheCapBytes;
612
+ return args.retention === "below-cap" ? Number.MAX_SAFE_INTEGER : undefined;
613
+ }
614
+
615
+ function installCacheCapOverride(args: CliArgs): () => void {
616
+ const override = cacheCapOverride(args);
617
+ if (override === undefined || args.baseline) return () => {};
618
+ const hooks = (sessionManagerModule as SessionManagerModuleWithTestHooks).SessionManagerTestHooks;
619
+ if (!hooks) {
620
+ throw new Error("--cache-cap-bytes and --retention below-cap require the current retention-policy branch.");
621
+ }
622
+ const previous = hooks.materializedCacheMaxBytesOverride;
623
+ hooks.materializedCacheMaxBytesOverride = override;
624
+ return () => {
625
+ hooks.materializedCacheMaxBytesOverride = previous;
626
+ };
627
+ }
628
+
629
+ async function runFreshOpenRss(args: CliArgs): Promise<FreshOpenRssWorkerResult> {
630
+ const sessionFile = args.freshOpenSessionFile;
631
+ if (!sessionFile) throw new Error("Fresh-open RSS worker requires a persisted session file.");
632
+ const fixture = fixtureFor(args);
633
+ const restoreCacheCap = installCacheCapOverride(args);
634
+ let manager: SessionManagerType | undefined;
635
+ try {
636
+ await settleFreshOpenRss();
637
+ const baseline = memorySample();
638
+ manager = await SessionManager.open(sessionFile);
639
+ const postOpen = memorySample();
640
+ const diskBytes = await residentCacheDiskBytes();
641
+ await settleFreshOpenRss();
642
+ const steadyState = memorySample();
643
+ const reclaim = await reclaimAfterMemoryPressure();
644
+ const postReclaim = memorySample();
645
+ const reclaimRssDeltaBytes = postReclaim.rssBytes - steadyState.rssBytes;
646
+ const stats = manager.getObservabilityStatsForTests();
647
+ return {
648
+ schemaVersion: SCHEMA_VERSION,
649
+ mode: "rss",
650
+ phase: "fresh-open",
651
+ metadata: metadata(),
652
+ fixture,
653
+ freshOpenPhase: {
654
+ baseline,
655
+ postOpen,
656
+ steadyState,
657
+ postReclaim,
658
+ steadyStateDelta: subtractMemorySamples(steadyState, baseline),
659
+ postReclaimDelta: subtractMemorySamples(postReclaim, baseline),
660
+ reclaim,
661
+ reclaimedBytes: Math.max(0, -reclaimRssDeltaBytes),
662
+ reclaimRssDeltaBytes,
663
+ observability: cacheBackingObservability(stats),
664
+ residentCacheDiskBytes: diskBytes,
665
+ residentStoreMode: args.forceMemoryOnly ? "forced-memory-fallback" : "disk-preferred",
666
+ },
667
+ };
668
+ } finally {
669
+ if (manager) await manager.close();
670
+ restoreCacheCap();
671
+ }
672
+ }
673
+
674
+ async function runRss(args: CliArgs): Promise<RssWorkerResult> {
675
+ const fixture = fixtureFor(args);
676
+ const restoreCacheCap = installCacheCapOverride(args);
677
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-resident-memory-bench-"));
678
+ let manager: SessionManagerType | undefined;
679
+ try {
680
+ await settleAndForceGcTwice();
681
+ const baseline = memorySample();
682
+ if (args.forceMemoryOnly) await forceResidentCacheMemoryFallback();
683
+ manager = SessionManager.create(root, path.join(root, "sessions"));
684
+ appendSyntheticEntries(manager, fixture);
685
+ const postAppend = memorySample();
686
+ const diskBytes = await residentCacheDiskBytes();
687
+ await settleAndForceGcTwice();
688
+ const steadyState = memorySample();
689
+ const reclaim = await reclaimAfterMemoryPressure();
690
+ const postReclaim = memorySample();
691
+ const reclaimRssDeltaBytes = postReclaim.rssBytes - steadyState.rssBytes;
692
+ // readPair returns only a scalar, so its caller-owned snapshots are out of scope before the GC turn.
693
+ const guard = readPair(manager);
694
+ const postRead = memorySample();
695
+ await settleAndForceGcTwice();
696
+ const postReadGc = memorySample();
697
+ const stats = manager.getObservabilityStatsForTests();
698
+ await manager.ensureOnDisk();
699
+ await manager.flush();
700
+ const postFlush = memorySample();
701
+ const sessionFile = manager.getSessionFile();
702
+ if (!sessionFile) throw new Error("RSS fixture did not persist a session file.");
703
+ const appendPhase: RssAppendPhase = {
704
+ baseline,
705
+ postAppend,
706
+ postFlush,
707
+ steadyState,
708
+ postReclaim,
709
+ postRead,
710
+ postReadGc,
711
+ steadyStateDelta: subtractMemorySamples(steadyState, baseline),
712
+ postReclaimDelta: subtractMemorySamples(postReclaim, baseline),
713
+ postReadGcDelta: subtractMemorySamples(postReadGc, baseline),
714
+ reclaim,
715
+ reclaimedBytes: Math.max(0, -reclaimRssDeltaBytes),
716
+ reclaimRssDeltaBytes,
717
+ observability: cacheBackingObservability(stats),
718
+ residentCacheDiskBytes: diskBytes,
719
+ residentStoreMode: args.forceMemoryOnly ? "forced-memory-fallback" : "disk-preferred",
720
+ guard,
721
+ };
722
+ await manager.close();
723
+ manager = undefined;
724
+ const freshOpen = await runFreshOpenRssChild(args, sessionFile);
725
+ return {
726
+ schemaVersion: SCHEMA_VERSION,
727
+ mode: "rss",
728
+ metadata: metadata(),
729
+ fixture,
730
+ appendPhase,
731
+ freshOpenPhase: freshOpen.freshOpenPhase,
732
+ };
733
+ } finally {
734
+ if (manager) await manager.close();
735
+ await fs.rm(root, { recursive: true, force: true });
736
+ restoreCacheCap();
737
+ }
738
+ }
739
+
740
+ function benchmarkStorePuts(
741
+ store: MemoryBlobStore | EphemeralBlobStore,
742
+ payloads: readonly Buffer[],
743
+ from: number,
744
+ count: number,
745
+ ): number[] {
746
+ const samples: number[] = [];
747
+ for (let index = 0; index < count; index++) {
748
+ const started = performance.now();
749
+ store.putSync(payloads[from + index]!);
750
+ samples.push(performance.now() - started);
751
+ }
752
+ return samples;
753
+ }
754
+
755
+ function benchmarkCanonicalMemoryStorePuts(payloads: readonly Buffer[], measureIterations: number): number[] {
756
+ const store = new MemoryBlobStore({ ownership: "canonical" });
757
+ benchmarkStorePuts(store, payloads, 0, PUT_WARMUP_ITERATIONS);
758
+ return benchmarkStorePuts(store, payloads, PUT_WARMUP_ITERATIONS, measureIterations);
759
+ }
760
+
761
+ function benchmarkAppendMessages(
762
+ manager: SessionManagerType,
763
+ payloads: readonly string[],
764
+ from: number,
765
+ count: number,
766
+ ): number[] {
767
+ const samples: number[] = [];
768
+ for (let index = 0; index < count; index++) {
769
+ const started = performance.now();
770
+ manager.appendMessage({ role: "user", content: payloads[from + index]!, timestamp: from + index });
771
+ samples.push(performance.now() - started);
772
+ }
773
+ return samples;
774
+ }
775
+
776
+ function entriesPerSecond(entries: number, wallMs: number): number {
777
+ return wallMs === 0 ? Number.POSITIVE_INFINITY : (entries * 1_000) / wallMs;
778
+ }
779
+
780
+ function measureFsyncCalls<T>(operation: () => T): { result: T; fsyncCalls: number } {
781
+ const require = createRequire(import.meta.url);
782
+ const mutableFs = require("node:fs") as typeof nodeFs;
783
+ const originalFsyncSync = mutableFs.fsyncSync;
784
+ let fsyncCalls = 0;
785
+ mutableFs.fsyncSync = ((fileDescriptor: number): void => {
786
+ fsyncCalls++;
787
+ return originalFsyncSync(fileDescriptor);
788
+ }) as typeof mutableFs.fsyncSync;
789
+ syncBuiltinESMExports();
790
+ try {
791
+ return { result: operation(), fsyncCalls };
792
+ } finally {
793
+ mutableFs.fsyncSync = originalFsyncSync;
794
+ syncBuiltinESMExports();
795
+ }
796
+ }
797
+
798
+ async function runPutLatency(args: CliArgs): Promise<PutLatencyWorkerResult> {
799
+ const measureIterations = args.puts ?? PUT_MEASURE_ITERATIONS;
800
+ const fixture: FixtureDimensions = {
801
+ seed: SYNTHETIC_SEED,
802
+ entries: measureIterations,
803
+ bytesPerEntry: PUT_BYTES,
804
+ };
805
+ const payloadBuffers = Array.from({ length: PUT_WARMUP_ITERATIONS + measureIterations }, (_, index) =>
806
+ syntheticBuffer(fixture.seed, index, fixture.bytesPerEntry),
807
+ );
808
+ const payloads = payloadBuffers.map(buffer => buffer.toString("utf8"));
809
+
810
+ const canonicalMemorySamples = benchmarkCanonicalMemoryStorePuts(payloadBuffers, measureIterations);
811
+ await settleAndForceGcTwice();
812
+
813
+ const directStoreRoot = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-resident-direct-put-bench-"));
814
+ let adoptedEphemeralStore: EphemeralBlobStore | undefined;
815
+ let adoptedEphemeralSamples: number[] = [];
816
+ let adoptedEphemeralPutFsyncCalls = 0;
817
+ try {
818
+ const instanceDir = openVerifiedResidentCacheInstanceDir(path.join(directStoreRoot, "resident-cache"));
819
+ adoptedEphemeralStore = EphemeralBlobStore.adoptVerifiedDir(instanceDir);
820
+ benchmarkStorePuts(adoptedEphemeralStore, payloadBuffers, 0, PUT_WARMUP_ITERATIONS);
821
+ const measurement = measureFsyncCalls(() =>
822
+ benchmarkStorePuts(adoptedEphemeralStore!, payloadBuffers, PUT_WARMUP_ITERATIONS, measureIterations),
823
+ );
824
+ adoptedEphemeralSamples = measurement.result;
825
+ adoptedEphemeralPutFsyncCalls = measurement.fsyncCalls;
826
+ if (adoptedEphemeralPutFsyncCalls !== 0) {
827
+ throw new Error(
828
+ `Adopted EphemeralBlobStore put path called fsyncSync ${adoptedEphemeralPutFsyncCalls} time(s).`,
829
+ );
830
+ }
831
+ } finally {
832
+ adoptedEphemeralStore?.dispose();
833
+ await fs.rm(directStoreRoot, { recursive: true, force: true });
834
+ }
835
+
836
+ const memoryManager = SessionManager.inMemory();
837
+ let memoryAppendSamples: number[];
838
+ let memoryEndToEndAppendWallMs = 0;
839
+ try {
840
+ benchmarkAppendMessages(memoryManager, payloads, 0, PUT_WARMUP_ITERATIONS);
841
+ const started = performance.now();
842
+ memoryAppendSamples = benchmarkAppendMessages(memoryManager, payloads, PUT_WARMUP_ITERATIONS, measureIterations);
843
+ memoryEndToEndAppendWallMs = performance.now() - started;
844
+ } finally {
845
+ await memoryManager.close();
846
+ }
847
+
848
+ const appendRoot = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-resident-append-bench-"));
849
+ let residentManager: SessionManagerType | undefined;
850
+ let residentAppendSamples: number[] = [];
851
+ let residentEndToEndAppendWallMs = 0;
852
+ let residentAppendFsyncCalls = 0;
853
+ try {
854
+ residentManager = SessionManager.create(appendRoot, path.join(appendRoot, "sessions"));
855
+ benchmarkAppendMessages(residentManager, payloads, 0, PUT_WARMUP_ITERATIONS);
856
+ const started = performance.now();
857
+ const measurement = measureFsyncCalls(() =>
858
+ benchmarkAppendMessages(residentManager!, payloads, PUT_WARMUP_ITERATIONS, measureIterations),
859
+ );
860
+ residentAppendSamples = measurement.result;
861
+ residentAppendFsyncCalls = measurement.fsyncCalls;
862
+ residentEndToEndAppendWallMs = performance.now() - started;
863
+ if (residentAppendFsyncCalls !== 0) {
864
+ throw new Error(`Resident SessionManager append path called fsyncSync ${residentAppendFsyncCalls} time(s).`);
865
+ }
866
+ } finally {
867
+ if (residentManager) await residentManager.close();
868
+ await fs.rm(appendRoot, { recursive: true, force: true });
869
+ }
870
+
871
+ const canonicalMemoryPutMs = summarize(canonicalMemorySamples);
872
+ const adoptedEphemeralPutMs = summarize(adoptedEphemeralSamples);
873
+ const memoryAppendMs = summarize(memoryAppendSamples);
874
+ const residentAppendMs = summarize(residentAppendSamples);
875
+ return {
876
+ schemaVersion: SCHEMA_VERSION,
877
+ mode: "put-latency",
878
+ metadata: metadata(),
879
+ fixture: { ...fixture, warmupIterations: PUT_WARMUP_ITERATIONS, measureIterations },
880
+ metrics: {
881
+ directStorePut: {
882
+ canonicalMemoryPutMs,
883
+ adoptedEphemeralPutMs,
884
+ adoptedEphemeralToCanonicalMemoryMedianRatio:
885
+ canonicalMemoryPutMs.median === 0
886
+ ? Number.POSITIVE_INFINITY
887
+ : adoptedEphemeralPutMs.median / canonicalMemoryPutMs.median,
888
+ adoptedEphemeralPutFsyncCalls,
889
+ },
890
+ e2eAppend: {
891
+ memoryAppendMs,
892
+ residentAppendMs,
893
+ residentToMemoryAppendMedianRatio:
894
+ memoryAppendMs.median === 0 ? Number.POSITIVE_INFINITY : residentAppendMs.median / memoryAppendMs.median,
895
+ memoryWallMs: memoryEndToEndAppendWallMs,
896
+ residentWallMs: residentEndToEndAppendWallMs,
897
+ memoryEntriesPerSecond: entriesPerSecond(measureIterations, memoryEndToEndAppendWallMs),
898
+ residentEntriesPerSecond: entriesPerSecond(measureIterations, residentEndToEndAppendWallMs),
899
+ residentToMemoryThroughputRatio:
900
+ memoryEndToEndAppendWallMs === 0
901
+ ? Number.POSITIVE_INFINITY
902
+ : residentEndToEndAppendWallMs / memoryEndToEndAppendWallMs,
903
+ residentAppendFsyncCalls,
904
+ },
905
+ },
906
+ guard:
907
+ canonicalMemorySamples.length +
908
+ adoptedEphemeralSamples.length +
909
+ memoryAppendSamples.length +
910
+ residentAppendSamples.length,
911
+ };
912
+ }
913
+
914
+ function baselineMarker(): string {
915
+ const prefix = "resident-cache-baseline:";
916
+ return `${prefix}${"m".repeat(Math.max(0, BASELINE_MARKER_BYTES - Buffer.byteLength(prefix, "utf8")))}`;
917
+ }
918
+
919
+ function validateReadChurn(result: ReadChurnWorkerResult): void {
920
+ const materializedDeltas = result.cycles.map(cycle => cycle.materializedEntriesCachePopulateDelta);
921
+ const contextDeltas = result.cycles.map(cycle => cycle.pathOnlyContextBuildDelta);
922
+ if (result.baseline === "forced-rebuild") {
923
+ if (materializedDeltas.some(delta => delta !== 1) || contextDeltas.some(delta => delta !== 1)) {
924
+ throw new Error(
925
+ `INVALID RUN: forced-rebuild must rebuild both caches exactly once per cycle; materialized=${JSON.stringify(materializedDeltas)} context=${JSON.stringify(contextDeltas)}`,
926
+ );
927
+ }
928
+ return;
929
+ }
930
+ if (result.fixture.retention === "below-cap") {
931
+ if (sum(materializedDeltas) !== 0 || sum(contextDeltas) !== 0) {
932
+ throw new Error(
933
+ `INVALID RUN: below-cap cache rebuilt after warmup; materialized=${JSON.stringify(materializedDeltas)} context=${JSON.stringify(contextDeltas)}`,
934
+ );
935
+ }
936
+ return;
937
+ }
938
+ if (sum(materializedDeltas) < 1) {
939
+ throw new Error(
940
+ `INVALID RUN: no rebuilds observed for materializedEntriesCachePopulateCount (above-cap); deltas=${JSON.stringify(materializedDeltas)}`,
941
+ );
942
+ }
943
+ if (sum(contextDeltas) < 1) {
944
+ throw new Error(
945
+ `INVALID RUN: no rebuilds observed for pathOnlyContextBuildCount (above-cap); deltas=${JSON.stringify(contextDeltas)}`,
946
+ );
947
+ }
948
+ }
949
+
950
+ async function runReadChurn(args: CliArgs): Promise<ReadChurnWorkerResult> {
951
+ const fixture = fixtureFor(args);
952
+ const restoreCacheCap = installCacheCapOverride(args);
953
+ try {
954
+ return await withPersistentFixture(fixture, false, async manager => {
955
+ const cycles: ReadChurnCycle[] = [];
956
+ let guard = 0;
957
+ for (let cycle = 0; cycle < READ_CHURN_CYCLES; cycle++) {
958
+ guard += readPair(manager);
959
+ if (args.baseline) {
960
+ manager.appendMessage({
961
+ role: "user",
962
+ content: baselineMarker(),
963
+ timestamp: fixture.entries + cycle,
964
+ });
965
+ }
966
+ if (!args.skipGc) await settleAndForceGc();
967
+ const before = manager.getObservabilityStatsForTests();
968
+ const cpuStarted = process.cpuUsage();
969
+ const wallStarted = performance.now();
970
+ guard += readPair(manager);
971
+ const wallMs = performance.now() - wallStarted;
972
+ const cpuMicros = process.cpuUsage(cpuStarted);
973
+ const after = manager.getObservabilityStatsForTests();
974
+ cycles.push({
975
+ cycle,
976
+ wallMs,
977
+ cpuMicros: cpuMicros.user + cpuMicros.system,
978
+ materializedEntriesCachePopulateDelta:
979
+ after.materializedEntriesCachePopulateCount - before.materializedEntriesCachePopulateCount,
980
+ pathOnlyContextBuildDelta: after.pathOnlyContextBuildCount - before.pathOnlyContextBuildCount,
981
+ });
982
+ }
983
+ const result: ReadChurnWorkerResult = {
984
+ schemaVersion: SCHEMA_VERSION,
985
+ mode: "read-churn",
986
+ metadata: metadata(),
987
+ fixture: {
988
+ ...fixture,
989
+ cycles: READ_CHURN_CYCLES,
990
+ retention: args.retention,
991
+ cacheCapBytes: cacheCapOverride(args) ?? "production-default",
992
+ },
993
+ baseline: args.baseline ? "forced-rebuild" : "none",
994
+ cycles,
995
+ metrics: {
996
+ cycleWallMs: summarize(cycles.map(cycle => cycle.wallMs)),
997
+ cycleCpuMicros: summarize(cycles.map(cycle => cycle.cpuMicros)),
998
+ materializedEntriesCachePopulateDelta: summarize(
999
+ cycles.map(cycle => cycle.materializedEntriesCachePopulateDelta),
1000
+ ),
1001
+ pathOnlyContextBuildDelta: summarize(cycles.map(cycle => cycle.pathOnlyContextBuildDelta)),
1002
+ aggregateWallMs: sum(cycles.map(cycle => cycle.wallMs)),
1003
+ aggregateCpuMicros: sum(cycles.map(cycle => cycle.cpuMicros)),
1004
+ aggregateMaterializedEntriesCachePopulateDelta: sum(
1005
+ cycles.map(cycle => cycle.materializedEntriesCachePopulateDelta),
1006
+ ),
1007
+ aggregatePathOnlyContextBuildDelta: sum(cycles.map(cycle => cycle.pathOnlyContextBuildDelta)),
1008
+ },
1009
+ guard,
1010
+ };
1011
+ validateReadChurn(result);
1012
+ return result;
1013
+ });
1014
+ } finally {
1015
+ restoreCacheCap();
1016
+ }
1017
+ }
1018
+
1019
+ async function runWorker(args: CliArgs): Promise<WorkerResult | FreshOpenRssWorkerResult> {
1020
+ switch (args.mode) {
1021
+ case "rss":
1022
+ return args.freshOpenSessionFile ? await runFreshOpenRss(args) : await runRss(args);
1023
+ case "put-latency":
1024
+ return await runPutLatency(args);
1025
+ case "read-churn":
1026
+ return await runReadChurn(args);
1027
+ }
1028
+ }
1029
+
1030
+ function childArguments(args: CliArgs): string[] {
1031
+ const argumentsForChild = [
1032
+ import.meta.path,
1033
+ "--worker",
1034
+ "--mode",
1035
+ args.mode,
1036
+ "--runs",
1037
+ "1",
1038
+ "--retention",
1039
+ args.retention,
1040
+ ];
1041
+ if (args.baseline) argumentsForChild.push("--baseline", "forced-rebuild");
1042
+ if (args.entries !== undefined) argumentsForChild.push("--entries", String(args.entries));
1043
+ if (args.bytesPerEntry !== undefined) argumentsForChild.push("--bytes-per-entry", String(args.bytesPerEntry));
1044
+ if (args.cacheCapBytes !== undefined) argumentsForChild.push("--cache-cap-bytes", String(args.cacheCapBytes));
1045
+ if (args.puts !== undefined) argumentsForChild.push("--puts", String(args.puts));
1046
+ if (args.skipGc) argumentsForChild.push("--skip-gc");
1047
+ if (args.forceMemoryOnly) argumentsForChild.push("--force-memory-only");
1048
+ return argumentsForChild;
1049
+ }
1050
+
1051
+ async function runChildProcess(argumentsForChild: readonly string[]): Promise<unknown> {
1052
+ const child = Bun.spawn([process.execPath, ...argumentsForChild], {
1053
+ cwd: process.cwd(),
1054
+ env: { ...process.env, NO_COLOR: "1" },
1055
+ stdout: "pipe",
1056
+ stderr: "pipe",
1057
+ });
1058
+ const [exitCode, stdout, stderr] = await Promise.all([
1059
+ child.exited,
1060
+ new Response(child.stdout).text(),
1061
+ new Response(child.stderr).text(),
1062
+ ]);
1063
+ if (exitCode !== 0) {
1064
+ const diagnostic = stderr.trim() || stdout.trim() || `resident-memory benchmark child exited ${exitCode}`;
1065
+ throw new Error(diagnostic);
1066
+ }
1067
+ try {
1068
+ return JSON.parse(stdout) as unknown;
1069
+ } catch (error) {
1070
+ throw new Error(`Could not parse resident-memory benchmark child output: ${String(error)}\n${stdout}`);
1071
+ }
1072
+ }
1073
+
1074
+ async function runFreshOpenRssChild(args: CliArgs, sessionFile: string): Promise<FreshOpenRssWorkerResult> {
1075
+ const result = await runChildProcess([...childArguments(args), "--fresh-open-session-file", sessionFile]);
1076
+ if (
1077
+ typeof result !== "object" ||
1078
+ result === null ||
1079
+ !("mode" in result) ||
1080
+ result.mode !== "rss" ||
1081
+ !("phase" in result) ||
1082
+ result.phase !== "fresh-open"
1083
+ ) {
1084
+ throw new Error("RSS fresh-open child returned an unexpected result.");
1085
+ }
1086
+ return result as FreshOpenRssWorkerResult;
1087
+ }
1088
+
1089
+ async function runChild(args: CliArgs): Promise<WorkerResult> {
1090
+ const result = await runChildProcess(childArguments(args));
1091
+ if (typeof result !== "object" || result === null || !("mode" in result) || "phase" in result) {
1092
+ throw new Error("Resident-memory benchmark child returned an unexpected result.");
1093
+ }
1094
+ return result as WorkerResult;
1095
+ }
1096
+
1097
+ function summarizeAc1(runs: readonly RssWorkerResult[]): {
1098
+ appendSteadyDelta: number;
1099
+ postReclaimDelta: number;
1100
+ reclaimedBytes: number;
1101
+ external: {
1102
+ steadyStateBytes: number;
1103
+ postReclaimBytes: number;
1104
+ postReclaimDelta: number;
1105
+ };
1106
+ arrayBuffers: {
1107
+ steadyStateBytes: number;
1108
+ postReclaimBytes: number;
1109
+ postReclaimDelta: number;
1110
+ };
1111
+ freshOpenDiagnostic: {
1112
+ steadyStateRssDelta: number;
1113
+ postReclaimRssDelta: number;
1114
+ classification: "documented-evidence";
1115
+ };
1116
+ verdict: "pass" | "documented-evidence-with-reclaim-proof" | "fail";
1117
+ } {
1118
+ const appendSteadyDelta = summarize(runs.map(run => run.appendPhase.steadyStateDelta.rssBytes));
1119
+ const postReclaimDelta = summarize(runs.map(run => run.appendPhase.postReclaimDelta.rssBytes));
1120
+ const reclaimedBytes = summarize(runs.map(run => run.appendPhase.reclaimedBytes));
1121
+ const steadyStateExternal = summarize(runs.map(run => run.appendPhase.steadyState.externalBytes));
1122
+ const postReclaimExternal = summarize(runs.map(run => run.appendPhase.postReclaim.externalBytes));
1123
+ const postReclaimExternalDelta = summarize(runs.map(run => run.appendPhase.postReclaimDelta.externalBytes));
1124
+ const steadyStateArrayBuffers = summarize(runs.map(run => run.appendPhase.steadyState.arrayBuffersBytes));
1125
+ const postReclaimArrayBuffers = summarize(runs.map(run => run.appendPhase.postReclaim.arrayBuffersBytes));
1126
+ const postReclaimArrayBuffersDelta = summarize(runs.map(run => run.appendPhase.postReclaimDelta.arrayBuffersBytes));
1127
+ const freshOpenRssDelta = summarize(runs.map(run => run.freshOpenPhase.steadyStateDelta.rssBytes));
1128
+ const freshOpenPostReclaimRssDelta = summarize(runs.map(run => run.freshOpenPhase.postReclaimDelta.rssBytes));
1129
+ const passesSteadyStateGate = appendSteadyDelta.median <= AC1_APPEND_PHASE_RSS_LIMIT_BYTES;
1130
+ const hasReclaimProof = postReclaimDelta.median <= AC1_APPEND_PHASE_RSS_LIMIT_BYTES && reclaimedBytes.median > 0;
1131
+ return {
1132
+ appendSteadyDelta: appendSteadyDelta.median,
1133
+ postReclaimDelta: postReclaimDelta.median,
1134
+ reclaimedBytes: reclaimedBytes.median,
1135
+ external: {
1136
+ steadyStateBytes: steadyStateExternal.median,
1137
+ postReclaimBytes: postReclaimExternal.median,
1138
+ postReclaimDelta: postReclaimExternalDelta.median,
1139
+ },
1140
+ arrayBuffers: {
1141
+ steadyStateBytes: steadyStateArrayBuffers.median,
1142
+ postReclaimBytes: postReclaimArrayBuffers.median,
1143
+ postReclaimDelta: postReclaimArrayBuffersDelta.median,
1144
+ },
1145
+ freshOpenDiagnostic: {
1146
+ steadyStateRssDelta: freshOpenRssDelta.median,
1147
+ postReclaimRssDelta: freshOpenPostReclaimRssDelta.median,
1148
+ classification: "documented-evidence",
1149
+ },
1150
+ verdict: passesSteadyStateGate ? "pass" : hasReclaimProof ? "documented-evidence-with-reclaim-proof" : "fail",
1151
+ };
1152
+ }
1153
+
1154
+ function summarizeAc2(runs: readonly PutLatencyWorkerResult[]): {
1155
+ directStorePut: {
1156
+ canonicalMemoryMedianMs: number;
1157
+ adoptedEphemeralMedianMs: number;
1158
+ adoptedEphemeralToCanonicalMemoryMedianRatio: number;
1159
+ };
1160
+ e2eAppend: {
1161
+ memoryAppendMedianMs: NumericSummary;
1162
+ residentAppendMedianMs: NumericSummary;
1163
+ residentToMemoryAppendMedianRatio: NumericSummary;
1164
+ memoryEntriesPerSecond: NumericSummary;
1165
+ residentEntriesPerSecond: NumericSummary;
1166
+ residentToMemoryThroughputRatio: NumericSummary;
1167
+ };
1168
+ verdict: "pass" | "documented-evidence";
1169
+ justification?: string;
1170
+ } {
1171
+ const canonicalMemoryMedianMs = summarize(runs.map(run => run.metrics.directStorePut.canonicalMemoryPutMs.median));
1172
+ const adoptedEphemeralMedianMs = summarize(runs.map(run => run.metrics.directStorePut.adoptedEphemeralPutMs.median));
1173
+ const directStoreRatio = summarize(
1174
+ runs.map(run => run.metrics.directStorePut.adoptedEphemeralToCanonicalMemoryMedianRatio),
1175
+ );
1176
+ const memoryAppendMedianMs = summarize(runs.map(run => run.metrics.e2eAppend.memoryAppendMs.median));
1177
+ const residentAppendMedianMs = summarize(runs.map(run => run.metrics.e2eAppend.residentAppendMs.median));
1178
+ const residentToMemoryAppendMedianRatio = summarize(
1179
+ runs.map(run => run.metrics.e2eAppend.residentToMemoryAppendMedianRatio),
1180
+ );
1181
+ const memoryEntriesPerSecond = summarize(runs.map(run => run.metrics.e2eAppend.memoryEntriesPerSecond));
1182
+ const residentEntriesPerSecond = summarize(runs.map(run => run.metrics.e2eAppend.residentEntriesPerSecond));
1183
+ const residentToMemoryThroughputRatio = summarize(
1184
+ runs.map(run => run.metrics.e2eAppend.residentToMemoryThroughputRatio),
1185
+ );
1186
+ const verdict = directStoreRatio.median <= AC2_DIRECT_STORE_MEDIAN_RATIO_LIMIT ? "pass" : "documented-evidence";
1187
+ return {
1188
+ directStorePut: {
1189
+ canonicalMemoryMedianMs: canonicalMemoryMedianMs.median,
1190
+ adoptedEphemeralMedianMs: adoptedEphemeralMedianMs.median,
1191
+ adoptedEphemeralToCanonicalMemoryMedianRatio: directStoreRatio.median,
1192
+ },
1193
+ e2eAppend: {
1194
+ memoryAppendMedianMs,
1195
+ residentAppendMedianMs,
1196
+ residentToMemoryAppendMedianRatio,
1197
+ memoryEntriesPerSecond,
1198
+ residentEntriesPerSecond,
1199
+ residentToMemoryThroughputRatio,
1200
+ },
1201
+ verdict,
1202
+ ...(verdict === "documented-evidence"
1203
+ ? {
1204
+ justification: `The ${directStoreRatio.median.toFixed(2)}x direct-store ratio compares canonical MemoryBlobStore putSync with adopted verified EphemeralBlobStore putSync over the same pre-generated unique Buffers. The resident path deliberately retains O_EXCL|O_NOFOLLOW, owner-only modes, and lazy EEXIST verification. Its ${adoptedEphemeralMedianMs.median.toFixed(4)} ms median sustains ${residentEntriesPerSecond.median.toFixed(0)} SessionManager appends/s; treat the ratio as a secure-write baseline artifact, not an end-to-end user-impact regression.`,
1205
+ }
1206
+ : {}),
1207
+ };
1208
+ }
1209
+
1210
+ function summarizeParentRuns(mode: Mode, runs: readonly WorkerResult[]): object {
1211
+ if (mode === "rss") {
1212
+ const rssRuns = runs as readonly RssWorkerResult[];
1213
+ return {
1214
+ appendPhase: {
1215
+ baseline: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.baseline)),
1216
+ postAppend: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.postAppend)),
1217
+ postFlush: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.postFlush)),
1218
+ steadyState: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.steadyState)),
1219
+ postReclaim: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.postReclaim)),
1220
+ postRead: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.postRead)),
1221
+ postReadGc: summarizeMemorySamples(rssRuns.map(run => run.appendPhase.postReadGc)),
1222
+ steadyStateDelta: summarizeMemoryDeltas(rssRuns.map(run => run.appendPhase.steadyStateDelta)),
1223
+ postReclaimDelta: summarizeMemoryDeltas(rssRuns.map(run => run.appendPhase.postReclaimDelta)),
1224
+ postReadGcDelta: summarizeMemoryDeltas(rssRuns.map(run => run.appendPhase.postReadGcDelta)),
1225
+ reclaim: rssRuns.map(run => run.appendPhase.reclaim),
1226
+ reclaimedBytes: summarize(rssRuns.map(run => run.appendPhase.reclaimedBytes)),
1227
+ reclaimRssDeltaBytes: summarize(rssRuns.map(run => run.appendPhase.reclaimRssDeltaBytes)),
1228
+ residentCacheDiskBytes: rssRuns.map(run => run.appendPhase.residentCacheDiskBytes),
1229
+ residentStoreModes: rssRuns.map(run => run.appendPhase.residentStoreMode),
1230
+ cacheBackingObservability: rssRuns.map(run => run.appendPhase.observability),
1231
+ },
1232
+ freshOpenPhase: {
1233
+ baseline: summarizeMemorySamples(rssRuns.map(run => run.freshOpenPhase.baseline)),
1234
+ postOpen: summarizeMemorySamples(rssRuns.map(run => run.freshOpenPhase.postOpen)),
1235
+ steadyState: summarizeMemorySamples(rssRuns.map(run => run.freshOpenPhase.steadyState)),
1236
+ postReclaim: summarizeMemorySamples(rssRuns.map(run => run.freshOpenPhase.postReclaim)),
1237
+ steadyStateDelta: summarizeMemoryDeltas(rssRuns.map(run => run.freshOpenPhase.steadyStateDelta)),
1238
+ postReclaimDelta: summarizeMemoryDeltas(rssRuns.map(run => run.freshOpenPhase.postReclaimDelta)),
1239
+ reclaim: rssRuns.map(run => run.freshOpenPhase.reclaim),
1240
+ reclaimedBytes: summarize(rssRuns.map(run => run.freshOpenPhase.reclaimedBytes)),
1241
+ reclaimRssDeltaBytes: summarize(rssRuns.map(run => run.freshOpenPhase.reclaimRssDeltaBytes)),
1242
+ residentCacheDiskBytes: rssRuns.map(run => run.freshOpenPhase.residentCacheDiskBytes),
1243
+ residentStoreModes: rssRuns.map(run => run.freshOpenPhase.residentStoreMode),
1244
+ cacheBackingObservability: rssRuns.map(run => run.freshOpenPhase.observability),
1245
+ },
1246
+ ac1: summarizeAc1(rssRuns),
1247
+ };
1248
+ }
1249
+ if (mode === "put-latency") {
1250
+ const putRuns = runs as readonly PutLatencyWorkerResult[];
1251
+ return {
1252
+ directStorePut: {
1253
+ canonicalMemoryPutMedianMs: summarize(
1254
+ putRuns.map(run => run.metrics.directStorePut.canonicalMemoryPutMs.median),
1255
+ ),
1256
+ adoptedEphemeralPutMedianMs: summarize(
1257
+ putRuns.map(run => run.metrics.directStorePut.adoptedEphemeralPutMs.median),
1258
+ ),
1259
+ adoptedEphemeralToCanonicalMemoryMedianRatio: summarize(
1260
+ putRuns.map(run => run.metrics.directStorePut.adoptedEphemeralToCanonicalMemoryMedianRatio),
1261
+ ),
1262
+ adoptedEphemeralPutFsyncCalls: putRuns.map(run => run.metrics.directStorePut.adoptedEphemeralPutFsyncCalls),
1263
+ },
1264
+ e2eAppend: {
1265
+ memoryAppendMedianMs: summarize(putRuns.map(run => run.metrics.e2eAppend.memoryAppendMs.median)),
1266
+ residentAppendMedianMs: summarize(putRuns.map(run => run.metrics.e2eAppend.residentAppendMs.median)),
1267
+ residentToMemoryAppendMedianRatio: summarize(
1268
+ putRuns.map(run => run.metrics.e2eAppend.residentToMemoryAppendMedianRatio),
1269
+ ),
1270
+ memoryWallMs: summarize(putRuns.map(run => run.metrics.e2eAppend.memoryWallMs)),
1271
+ residentWallMs: summarize(putRuns.map(run => run.metrics.e2eAppend.residentWallMs)),
1272
+ memoryEntriesPerSecond: summarize(putRuns.map(run => run.metrics.e2eAppend.memoryEntriesPerSecond)),
1273
+ residentEntriesPerSecond: summarize(putRuns.map(run => run.metrics.e2eAppend.residentEntriesPerSecond)),
1274
+ residentToMemoryThroughputRatio: summarize(
1275
+ putRuns.map(run => run.metrics.e2eAppend.residentToMemoryThroughputRatio),
1276
+ ),
1277
+ residentAppendFsyncCalls: putRuns.map(run => run.metrics.e2eAppend.residentAppendFsyncCalls),
1278
+ },
1279
+ ac2: summarizeAc2(putRuns),
1280
+ };
1281
+ }
1282
+ const churnRuns = runs as readonly ReadChurnWorkerResult[];
1283
+ const cycles = churnRuns.flatMap(run => run.cycles);
1284
+ return {
1285
+ cycleWallMs: summarize(cycles.map(cycle => cycle.wallMs)),
1286
+ cycleCpuMicros: summarize(cycles.map(cycle => cycle.cpuMicros)),
1287
+ materializedEntriesCachePopulateDelta: summarize(
1288
+ cycles.map(cycle => cycle.materializedEntriesCachePopulateDelta),
1289
+ ),
1290
+ pathOnlyContextBuildDelta: summarize(cycles.map(cycle => cycle.pathOnlyContextBuildDelta)),
1291
+ aggregateWallMs: summarize(churnRuns.map(run => run.metrics.aggregateWallMs)),
1292
+ aggregateCpuMicros: summarize(churnRuns.map(run => run.metrics.aggregateCpuMicros)),
1293
+ aggregateMaterializedEntriesCachePopulateDelta: summarize(
1294
+ churnRuns.map(run => run.metrics.aggregateMaterializedEntriesCachePopulateDelta),
1295
+ ),
1296
+ aggregatePathOnlyContextBuildDelta: summarize(
1297
+ churnRuns.map(run => run.metrics.aggregatePathOnlyContextBuildDelta),
1298
+ ),
1299
+ };
1300
+ }
1301
+
1302
+ async function runParent(args: CliArgs): Promise<void> {
1303
+ const runs: WorkerResult[] = [];
1304
+ for (let run = 0; run < args.runs; run++) {
1305
+ runs.push(await runChild(args));
1306
+ }
1307
+ const summary = summarizeParentRuns(args.mode, runs);
1308
+ const acceptanceReport =
1309
+ args.mode === "rss"
1310
+ ? { ac1: summarizeAc1(runs as readonly RssWorkerResult[]) }
1311
+ : args.mode === "put-latency"
1312
+ ? { ac2: summarizeAc2(runs as readonly PutLatencyWorkerResult[]) }
1313
+ : {};
1314
+ const output = {
1315
+ schemaVersion: SCHEMA_VERSION,
1316
+ mode: args.mode,
1317
+ runs: args.runs,
1318
+ baseline: args.baseline ? "forced-rebuild" : "none",
1319
+ metadata: metadata(),
1320
+ fixture: fixtureForMode(args),
1321
+ summary,
1322
+ ...acceptanceReport,
1323
+ runResults: runs,
1324
+ };
1325
+ process.stdout.write(`${JSON.stringify(output)}\n`);
1326
+ }
1327
+
1328
+ async function main(): Promise<void> {
1329
+ const args = parseArgs(Bun.argv.slice(2));
1330
+ if (args.worker) {
1331
+ const result = await runWorker(args);
1332
+ process.stdout.write(`${JSON.stringify(result)}\n`);
1333
+ return;
1334
+ }
1335
+ await runParent(args);
1336
+ }
1337
+
1338
+ await main().catch(error => {
1339
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
1340
+ process.exitCode = 1;
1341
+ });