Skip to content

Commit 127e174

Browse files
authored
refactor: route auto-reply sessions through session seam (#89124)
* clawdbot-6f0: route agent runtime session writes through seam * clawdbot-6f0: route command entry persistence through seam * test: ratchet auto-reply session accessor writes * refactor: scope embedded attempt quota reads * test: ratchet session store save writer
1 parent 21e3cfa commit 127e174

35 files changed

Lines changed: 1047 additions & 354 deletions

scripts/check-session-accessor-boundary.mjs

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,24 @@ const legacyWholeStoreAccessNames = new Set([
2323
"saveSessionStore",
2424
"updateSessionStore",
2525
]);
26+
const legacyWriterNames = new Set([
27+
"applySessionStoreEntryPatch",
28+
"saveSessionStore",
29+
"updateSessionStore",
30+
"updateSessionStoreEntry",
31+
]);
2632

2733
export const migratedSessionAccessorFiles = new Set([
2834
"src/agents/embedded-agent-runner/compaction-successor-transcript.ts",
35+
"src/agents/embedded-agent-runner/run/attempt.ts",
2936
"src/agents/embedded-agent-runner/tool-result-truncation.ts",
3037
"src/agents/embedded-agent-runner/transcript-rewrite.ts",
3138
"src/agents/embedded-agent-runner/transcript-runtime-state.ts",
39+
"src/auto-reply/reply/agent-runner-helpers.ts",
40+
"src/auto-reply/reply/agent-runner.ts",
41+
"src/auto-reply/reply/commands-subagents/action-info.ts",
42+
"src/auto-reply/reply/followup-runner.ts",
43+
"src/auto-reply/reply/queue/drain.ts",
3244
"src/commands/export-trajectory.ts",
3345
"src/commands/health.ts",
3446
"src/commands/sandbox-explain.ts",
@@ -52,6 +64,30 @@ export const migratedBundledPluginSessionAccessorFiles = new Set([
5264
"extensions/telegram/src/bot-handlers.runtime.ts",
5365
]);
5466

67+
export const migratedSessionAccessorWriteFiles = new Set([
68+
"src/agents/command/attempt-execution.shared.ts",
69+
"src/agents/command/session-store.ts",
70+
"src/agents/embedded-agent-runner/run.ts",
71+
"src/agents/embedded-agent-runner/run/attempt.ts",
72+
"src/auto-reply/reply/abort-cutoff.runtime.ts",
73+
"src/auto-reply/reply/agent-runner-cli-dispatch.ts",
74+
"src/auto-reply/reply/agent-runner-execution.ts",
75+
"src/auto-reply/reply/agent-runner-memory.ts",
76+
"src/auto-reply/reply/agent-runner.ts",
77+
"src/auto-reply/reply/body.ts",
78+
"src/auto-reply/reply/commands-acp/lifecycle.ts",
79+
"src/auto-reply/reply/commands-reset.ts",
80+
"src/auto-reply/reply/directive-handling.impl.ts",
81+
"src/auto-reply/reply/directive-handling.persist.ts",
82+
"src/auto-reply/reply/dispatch-from-config.runtime.ts",
83+
"src/auto-reply/reply/followup-runner.ts",
84+
"src/auto-reply/reply/get-reply.ts",
85+
"src/auto-reply/reply/model-selection.ts",
86+
"src/auto-reply/reply/session-reset-model.ts",
87+
"src/auto-reply/reply/session-updates.ts",
88+
"src/auto-reply/reply/session-usage.ts",
89+
]);
90+
5591
function normalizeRelativePath(filePath) {
5692
return filePath.replaceAll(path.sep, "/");
5793
}
@@ -91,9 +127,8 @@ function bindingName(node) {
91127
return null;
92128
}
93129

94-
export function findSessionAccessorBoundaryViolations(content, fileName = "source.ts") {
130+
function findNamedSessionStoreViolations(content, fileName, legacyNames, legacyKind) {
95131
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
96-
const legacyNames = legacyNamesForFile(fileName);
97132
const violations = [];
98133

99134
const visit = (node) => {
@@ -105,7 +140,7 @@ export function findSessionAccessorBoundaryViolations(content, fileName = "sourc
105140
if (legacyNames.has(importedName)) {
106141
violations.push({
107142
line: toLine(sourceFile, specifier),
108-
reason: `imports legacy session store access "${importedName}"`,
143+
reason: `imports legacy session store ${legacyKind} "${importedName}"`,
109144
});
110145
}
111146
}
@@ -117,15 +152,15 @@ export function findSessionAccessorBoundaryViolations(content, fileName = "sourc
117152
if (name && legacyNames.has(name)) {
118153
violations.push({
119154
line: toLine(sourceFile, node),
120-
reason: `aliases legacy session store access "${name}"`,
155+
reason: `aliases legacy session store ${legacyKind} "${name}"`,
121156
});
122157
}
123158
}
124159

125160
if (ts.isPropertyAccessExpression(node) && legacyNames.has(node.name.text)) {
126161
violations.push({
127162
line: toLine(sourceFile, node.name),
128-
reason: `references legacy session store access "${node.name.text}"`,
163+
reason: `references legacy session store ${legacyKind} "${node.name.text}"`,
129164
});
130165
}
131166

@@ -136,7 +171,7 @@ export function findSessionAccessorBoundaryViolations(content, fileName = "sourc
136171
) {
137172
violations.push({
138173
line: toLine(sourceFile, node.argumentExpression),
139-
reason: `references legacy session store access "${node.argumentExpression.text}"`,
174+
reason: `references legacy session store ${legacyKind} "${node.argumentExpression.text}"`,
140175
});
141176
}
142177

@@ -149,7 +184,7 @@ export function findSessionAccessorBoundaryViolations(content, fileName = "sourc
149184
) {
150185
violations.push({
151186
line: toLine(sourceFile, node.expression),
152-
reason: `calls legacy session store access "${calleeName}"`,
187+
reason: `calls legacy session store ${legacyKind} "${calleeName}"`,
153188
});
154189
}
155190
}
@@ -161,21 +196,33 @@ export function findSessionAccessorBoundaryViolations(content, fileName = "sourc
161196
return violations;
162197
}
163198

199+
export function findSessionAccessorBoundaryViolations(content, fileName = "source.ts") {
200+
const legacyNames = legacyNamesForFile(fileName);
201+
const legacyKind = legacyNames === legacyWholeStoreAccessNames ? "access" : "reader";
202+
return findNamedSessionStoreViolations(content, fileName, legacyNames, legacyKind);
203+
}
204+
205+
export function findSessionAccessorWriteBoundaryViolations(content, fileName = "source.ts") {
206+
return findNamedSessionStoreViolations(content, fileName, legacyWriterNames, "writer");
207+
}
208+
164209
export async function main() {
165210
const repoRoot = resolveRepoRoot(import.meta.url);
166-
const sourceRoots = resolveSourceRoots(repoRoot, [
211+
const readSourceRoots = resolveSourceRoots(repoRoot, [
167212
"extensions/discord/src/monitor",
168213
"extensions/telegram/src",
169-
"src/agents/embedded-agent-runner",
214+
"src/agents",
215+
"src/auto-reply",
170216
"src/commands",
171217
"src/config/sessions",
172218
"src/cron",
173219
"src/gateway",
174220
"src/infra",
175221
]);
176-
const violations = await collectFileViolations({
222+
const writeSourceRoots = resolveSourceRoots(repoRoot, ["src/agents", "src/auto-reply"]);
223+
const readViolations = await collectFileViolations({
177224
repoRoot,
178-
sourceRoots,
225+
sourceRoots: readSourceRoots,
179226
skipFile: (filePath) => {
180227
const relativePath = normalizeRelativePath(path.relative(repoRoot, filePath));
181228
return (
@@ -185,18 +232,28 @@ export async function main() {
185232
},
186233
findViolations: findSessionAccessorBoundaryViolations,
187234
});
235+
const writeViolations = await collectFileViolations({
236+
repoRoot,
237+
sourceRoots: writeSourceRoots,
238+
skipFile: (filePath) =>
239+
!migratedSessionAccessorWriteFiles.has(
240+
normalizeRelativePath(path.relative(repoRoot, filePath)),
241+
),
242+
findViolations: findSessionAccessorWriteBoundaryViolations,
243+
});
244+
const violations = [...readViolations, ...writeViolations];
188245

189246
if (violations.length === 0) {
190247
console.log("session accessor boundary guard passed.");
191248
return;
192249
}
193250

194-
console.error("Found legacy session store access usage in session-accessor migrated files:");
251+
console.error("Found legacy session store usage in session-accessor migrated files:");
195252
for (const violation of violations) {
196253
console.error(`- ${violation.path}:${violation.line}: ${violation.reason}`);
197254
}
198255
console.error(
199-
"Use src/config/sessions/session-accessor.ts helpers for migrated paths. Expand this ratchet only after a slice migrates more files.",
256+
"Use src/config/sessions/session-accessor.ts helpers for migrated read/write paths. Expand this ratchet only after a slice migrates more files.",
200257
);
201258
process.exit(1);
202259
}

src/agents/command/attempt-execution.shared.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Shared session persistence and prompt-body helpers for agent attempt
33
* execution paths.
44
*/
5-
import { updateSessionStore } from "../../config/sessions/store.js";
5+
import { patchSessionEntry } from "../../config/sessions/session-accessor.js";
66
import { mergeSessionEntry, type SessionEntry } from "../../config/sessions/types.js";
77
import {
88
formatAgentInternalEventsForPlainPrompt,
@@ -34,16 +34,19 @@ function normalizeTranscriptMarkerUpdatedAt(value: number | undefined): number |
3434
export async function persistSessionEntry(
3535
params: PersistSessionEntryParams,
3636
): Promise<SessionEntry | undefined> {
37-
const persisted = await updateSessionStore(
38-
params.storePath,
39-
(store) => {
40-
const current = store[params.sessionKey];
41-
if (params.shouldPersist && !params.shouldPersist(current)) {
42-
return current;
37+
let rejectedMissingEntry = false;
38+
const persisted = await patchSessionEntry(
39+
{ sessionKey: params.sessionKey, storePath: params.storePath },
40+
(_entry, context) => {
41+
if (params.shouldPersist && !params.shouldPersist(context.existingEntry)) {
42+
rejectedMissingEntry = !context.existingEntry;
43+
return null;
4344
}
44-
const merged = mergeSessionEntry(store[params.sessionKey], params.entry);
45+
const merged = mergeSessionEntry(context.existingEntry, params.entry);
4546
if (params.preserveTranscriptMarkerUpdatedAt) {
46-
const currentUpdatedAt = normalizeTranscriptMarkerUpdatedAt(current?.updatedAt);
47+
const currentUpdatedAt = normalizeTranscriptMarkerUpdatedAt(
48+
context.existingEntry?.updatedAt,
49+
);
4750
const markerUpdatedAt = normalizeTranscriptMarkerUpdatedAt(params.entry.updatedAt);
4851
if (markerUpdatedAt !== undefined) {
4952
merged.updatedAt = Math.max(currentUpdatedAt ?? 0, markerUpdatedAt);
@@ -56,21 +59,23 @@ export async function persistSessionEntry(
5659
Reflect.deleteProperty(merged, field);
5760
}
5861
}
59-
store[params.sessionKey] = merged;
6062
return merged;
6163
},
6264
{
63-
resolveSingleEntryPersistence: (entry) =>
64-
entry ? { sessionKey: params.sessionKey, entry } : null,
65-
takeCacheOwnership: true,
65+
fallbackEntry: params.sessionStore[params.sessionKey] ?? params.entry,
66+
replaceEntry: true,
6667
},
6768
);
69+
if (rejectedMissingEntry) {
70+
delete params.sessionStore[params.sessionKey];
71+
return undefined;
72+
}
6873
if (persisted) {
6974
params.sessionStore[params.sessionKey] = persisted;
7075
} else {
7176
delete params.sessionStore[params.sessionKey];
7277
}
73-
return persisted;
78+
return persisted ?? undefined;
7479
}
7580

7681
/** Prepends hidden internal event context unless the body already carries it. */

0 commit comments

Comments
 (0)