@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
393 lines • 15.3 kB
JavaScript
import { randomUUID } from '@openai/agents-core/_shims';
import { z } from 'zod';
import { UserError } from "../../errors.mjs";
import logger, { logModelAndToolActionWarning } from "../../logger.mjs";
import { Manifest } from "../manifest.mjs";
import { registerSandboxPreStopHook } from "../runtime/sessionLifecycle.mjs";
import { withSandboxSpan } from "../runtime/spans.mjs";
import { stableJsonStringify } from "../shared/stableJson.mjs";
import { buildMemoryRolloutPayload, normalizeRolloutSlug, renderPhaseOnePrompt, resolveMemoryRolloutId, } from "./rollouts.mjs";
import { joinRelativePaths, SandboxMemoryStorage } from "./storage.mjs";
import { renderMemoryConsolidationPrompt, renderRolloutExtractionInstructions, renderRolloutExtractionUserPrompt, } from "./prompts.mjs";
const RolloutExtractionArtifactsSchema = z.object({
rollout_summary: z.string(),
rollout_slug: z.string(),
raw_memory: z.string(),
});
const managersBySession = new WeakMap();
const objectIds = new WeakMap();
let nextObjectId = 1;
export function getOrCreateSandboxMemoryGenerationManager(args) {
if (args.memory.generate === null) {
throw new UserError('SandboxMemoryGenerationManager requires Memory.generate to be enabled.');
}
const layout = normalizedLayout(args.memory);
const layoutKey = memoryLayoutKey(args.memory);
let registry = managersBySession.get(args.session);
const existing = registry?.get(layoutKey);
if (existing) {
const nextSignature = memoryGenerationSignature(args.memory.generate);
if (existing.generationSignature !== nextSignature) {
throw new UserError('Sandbox session already has a different Memory generation config attached for this memory layout.');
}
if (existing.manager.isStopped()) {
registry.delete(layoutKey);
if (registry.size === 0) {
managersBySession.delete(args.session);
registry = undefined;
}
}
else {
existing.manager.updateRuntimeContext({
runAs: args.runAs,
runAgent: args.runAgent,
tracingParent: args.tracingParent,
});
return existing.manager;
}
}
if (registry) {
for (const entry of registry.values()) {
if (entry.layout.memoriesDir === layout.memoriesDir) {
throw new UserError(`Sandbox session already has a Memory generation capability for memoriesDir=${layout.memoriesDir}. Use a different memoriesDir for isolated memories, or the same layout to share memory.`);
}
if (entry.layout.sessionsDir === layout.sessionsDir) {
throw new UserError(`Sandbox session already has a Memory generation capability for sessionsDir=${layout.sessionsDir}. Use a different sessionsDir for isolated memories, or the same layout to share memory.`);
}
}
}
const manager = new SandboxMemoryGenerationManager(args);
registry ??= new Map();
registry.set(layoutKey, {
memory: args.memory,
manager,
generationSignature: memoryGenerationSignature(args.memory.generate),
layout,
});
managersBySession.set(args.session, registry);
return manager;
}
export class SandboxMemoryGenerationManager {
session;
memory;
runAs;
runAgent;
tracingParent;
storage;
pendingRolloutIds = new Set();
flushPromise;
stopped = false;
unregisterPreStopHook;
constructor(args) {
this.session = args.session;
this.memory = args.memory;
this.runAs = args.runAs;
this.runAgent = args.runAgent;
this.tracingParent = args.tracingParent;
this.storage = new SandboxMemoryStorage({
session: args.session,
runAs: args.runAs,
store: args.memory.store,
tracingParent: args.tracingParent,
});
this.unregisterPreStopHook = registerSandboxPreStopHook(this.session, async () => {
await this.flush();
});
}
isStopped() {
return this.stopped;
}
updateRuntimeContext(args) {
this.runAs = args.runAs;
this.runAgent = args.runAgent;
this.tracingParent = args.tracingParent;
this.storage.updateRuntimeContext({
runAs: args.runAs,
tracingParent: args.tracingParent,
});
}
async enqueueState(state, args) {
if (this.memory.generate === null || this.stopped) {
return;
}
const rolloutId = resolveMemoryRolloutId({
...args.rolloutIdentity,
fallbackId: randomUUID(),
});
const payload = buildMemoryRolloutPayload(state, {
rolloutId,
exception: args.exception,
inputOverride: args.inputOverride,
});
await withSandboxSpan('sandbox.memory.enqueue_rollout', {
rollout_id: rolloutId,
}, async () => {
await this.storage.ensureLayout(this.memory);
await this.storage.appendJsonl(joinRelativePaths(this.memory.layout.sessionsDir, `${rolloutId}.jsonl`), payload);
this.pendingRolloutIds.add(rolloutId);
}, this.tracingParent);
}
async flush() {
if (this.flushPromise) {
await this.flushPromise;
return;
}
this.flushPromise = this.flushOnce();
try {
await this.flushPromise;
}
finally {
this.flushPromise = undefined;
}
}
async flushOnce() {
if (this.memory.generate === null) {
return;
}
if (this.stopped) {
this.unregister();
return;
}
this.stopped = true;
try {
await withSandboxSpan('sandbox.memory.flush', {
rollout_count: this.pendingRolloutIds.size,
}, async () => {
await this.storage.ensureLayout(this.memory);
let wroteArtifacts = false;
for (const rolloutId of [...this.pendingRolloutIds].sort()) {
try {
const extraction = await this.runPhaseOne(rolloutId);
if (!validateRolloutArtifacts(extraction)) {
continue;
}
await this.persistPhaseOneArtifacts(rolloutId, extraction);
wroteArtifacts = true;
}
catch (error) {
logModelAndToolActionWarning(logger, 'Sandbox memory phase 1 failed:', error, { rolloutId });
}
}
if (wroteArtifacts) {
try {
await this.runPhaseTwo();
}
catch (error) {
logModelAndToolActionWarning(logger, 'Sandbox memory phase 2 failed:', error);
}
}
}, this.tracingParent);
}
finally {
this.pendingRolloutIds.clear();
this.unregister();
}
}
async runPhaseOne(rolloutId) {
const generate = this.memory.generate;
if (generate === null) {
throw new UserError('Memory generation is disabled.');
}
const transcriptPath = joinRelativePaths(this.memory.layout.sessionsDir, `${rolloutId}.jsonl`);
const transcript = await this.storage.readText(transcriptPath);
if (!transcript) {
throw new UserError(`Memory rollout transcript was not found: ${transcriptPath}`);
}
const { SandboxAgent } = await import("../agent.mjs");
const phaseOneAgent = new SandboxAgent({
name: 'sandbox-memory-phase-one',
instructions: renderRolloutExtractionInstructions(generate.extraPrompt),
model: generate.phaseOneModel,
modelSettings: generate.phaseOneModelSettings,
outputType: RolloutExtractionArtifactsSchema,
defaultManifest: this.currentManifest(),
runAs: this.runAs,
});
const result = await this.runAgent(phaseOneAgent, renderRolloutExtractionUserPrompt({
terminalMetadataJson: renderPhaseOnePrompt(transcript),
rolloutContents: transcript,
}), {
sandbox: {
session: this.session,
manifest: this.currentManifest(),
},
maxTurns: 500,
});
return RolloutExtractionArtifactsSchema.parse(result.finalOutput);
}
async persistPhaseOneArtifacts(rolloutId, extraction) {
const memoriesDir = this.memory.layout.memoriesDir;
const transcriptPath = joinRelativePaths(this.memory.layout.sessionsDir, `${rolloutId}.jsonl`);
const transcript = await this.storage.readText(transcriptPath);
const metadata = rolloutMetadataFromTranscript(transcript ?? '');
const slug = normalizeRolloutSlug(extraction.rollout_slug);
const rolloutPath = joinRelativePaths(this.memory.layout.sessionsDir, `${rolloutId}.jsonl`);
const rolloutSummaryFile = joinRelativePaths('rollout_summaries', `${rolloutId}_${slug}.md`);
await this.storage.writeText(joinRelativePaths(memoriesDir, 'raw_memories', `${rolloutId}.md`), formatRawMemory({
updatedAt: metadata.updatedAt,
rolloutId,
rolloutPath,
rolloutSummaryFile,
terminalState: metadata.terminalState,
rawMemory: extraction.raw_memory,
}));
await this.storage.writeText(joinRelativePaths(memoriesDir, rolloutSummaryFile), formatRolloutSummary({
updatedAt: metadata.updatedAt,
rolloutPath,
sessionId: String(this.session.state.sessionId ?? ''),
terminalState: metadata.terminalState,
rolloutSummary: extraction.rollout_summary,
}));
}
async runPhaseTwo() {
const generate = this.memory.generate;
if (generate === null) {
throw new UserError('Memory generation is disabled.');
}
const selection = await this.storage.buildPhaseTwoInputSelection({
memoriesDir: this.memory.layout.memoriesDir,
maxRawMemoriesForConsolidation: generate.maxRawMemoriesForConsolidation ?? 256,
});
if (!(await this.storage.rebuildRawMemories({
memoriesDir: this.memory.layout.memoriesDir,
selectedItems: selection.selected,
}))) {
return;
}
const { SandboxAgent } = await import("../agent.mjs");
const phaseTwoAgent = new SandboxAgent({
name: 'sandbox-memory-phase-two',
model: generate.phaseTwoModel,
modelSettings: generate.phaseTwoModelSettings,
defaultManifest: this.currentManifest(),
runAs: this.runAs,
});
await this.runAgent(phaseTwoAgent, renderMemoryConsolidationPrompt({
memoryRoot: this.memory.layout.memoriesDir,
selection,
extraPrompt: generate.extraPrompt,
}), {
sandbox: {
session: this.session,
manifest: this.currentManifest(),
},
maxTurns: generate.phaseTwoMaxTurns ?? 500,
});
await this.storage.writePhaseTwoSelection({
memoriesDir: this.memory.layout.memoriesDir,
selectedItems: selection.selected,
});
}
currentManifest() {
return new Manifest(this.session.state.manifest);
}
unregister() {
this.unregisterPreStopHook?.();
this.unregisterPreStopHook = undefined;
const registry = managersBySession.get(this.session);
if (!registry) {
return;
}
const layoutKey = memoryLayoutKey(this.memory);
const existing = registry.get(layoutKey);
if (existing?.manager === this) {
registry.delete(layoutKey);
}
if (registry.size === 0) {
managersBySession.delete(this.session);
}
}
}
function validateRolloutArtifacts(artifacts) {
const hasSlug = artifacts.rollout_slug.trim().length > 0;
const hasSummary = artifacts.rollout_summary.trim().length > 0;
const hasRawMemory = artifacts.raw_memory.trim().length > 0;
if (!hasSlug && !hasSummary && !hasRawMemory) {
return false;
}
if (!hasSlug || !hasSummary || !hasRawMemory) {
throw new UserError('Phase 1 returned partially-empty memory artifacts.');
}
normalizeRolloutSlug(artifacts.rollout_slug);
return true;
}
function rolloutMetadataFromTranscript(transcript) {
const payloads = transcript
.split('\n')
.filter((line) => line.trim().length > 0)
.map((line) => JSON.parse(line));
const lastPayload = payloads[payloads.length - 1];
if (!lastPayload) {
return {
updatedAt: 'unknown',
terminalState: 'unknown',
};
}
const terminalMetadata = lastPayload.terminal_metadata;
const terminalState = terminalMetadata && typeof terminalMetadata === 'object'
? String(terminalMetadata.terminal_state ??
'unknown')
: 'unknown';
return {
updatedAt: typeof lastPayload.updated_at === 'string'
? lastPayload.updated_at
: 'unknown',
terminalState,
};
}
function formatRawMemory(args) {
return [
`rollout_id: ${args.rolloutId}`,
`updated_at: ${args.updatedAt}`,
`rollout_path: ${args.rolloutPath}`,
`rollout_summary_file: ${args.rolloutSummaryFile}`,
`terminal_state: ${args.terminalState}`,
'',
args.rawMemory.trimEnd(),
'',
].join('\n');
}
function formatRolloutSummary(args) {
return [
`session_id: ${args.sessionId}`,
`updated_at: ${args.updatedAt}`,
`rollout_path: ${args.rolloutPath}`,
`terminal_state: ${args.terminalState}`,
'',
args.rolloutSummary.trimEnd(),
'',
].join('\n');
}
function normalizedLayout(memory) {
return {
memoriesDir: normalizeLayoutPath(memory.layout.memoriesDir),
sessionsDir: normalizeLayoutPath(memory.layout.sessionsDir),
summaryFile: normalizeLayoutPath(memory.layout.summaryFile),
};
}
function memoryLayoutKey(memory) {
const layout = normalizedLayout(memory);
return `${layout.memoriesDir}\0${layout.sessionsDir}\0${layout.summaryFile}`;
}
function normalizeLayoutPath(value) {
return value
.split('/')
.filter((part) => part.length > 0 && part !== '.')
.join('/');
}
function memoryGenerationSignature(generate) {
return stableJsonStringify(generate, {
encodeFunction: (value) => `function:${value.name || 'anonymous'}`,
encodeNonPlainObject: (value) => `object:${objectId(value)}`,
});
}
function objectId(value) {
const existing = objectIds.get(value);
if (existing) {
return existing;
}
const id = nextObjectId++;
objectIds.set(value, id);
return id;
}
//# sourceMappingURL=generation.mjs.map