@@ -17,6 +17,12 @@ import {
1717} from '../client/dws'
1818import { ROOMS } from '../constants'
1919import { createLogger , type Logger } from './logger'
20+ import {
21+ createMemoryPersistence ,
22+ type MemoryPersistenceService ,
23+ type CommitResult ,
24+ createWalletSigner ,
25+ } from './memory-persistence'
2026
2127// Store the original Eliza action handlers
2228type ElizaActionHandler = Action [ 'handler' ]
@@ -45,6 +51,16 @@ export interface RuntimeConfig {
4551 privateKey ?: Hex
4652 /** Network to connect to */
4753 network ?: NetworkType
54+ /** On-chain agent ID (bigint) for memory persistence */
55+ onChainAgentId ?: bigint
56+ /** Character CID on IPFS */
57+ characterCid ?: string
58+ /** Current state CID on IPFS */
59+ stateCid ?: string
60+ /** Enable memory persistence to L2/IPFS (default: true if configured) */
61+ enableMemoryPersistence ?: boolean
62+ /** Snapshot interval in ms (default: 60000) */
63+ memorySnapshotIntervalMs ?: number
4864}
4965
5066export interface RuntimeMessage {
@@ -138,6 +154,9 @@ export class CrucibleAgentRuntime {
138154 // Jeju service instance
139155 private jejuService : StandaloneJejuService | null = null
140156
157+ // Memory persistence service for L2/IPFS commits
158+ private memoryPersistence : MemoryPersistenceService | null = null
159+
141160 constructor ( config : RuntimeConfig ) {
142161 this . config = config
143162 this . log = config . logger ?? createLogger ( `Runtime:${ config . agentId } ` )
@@ -216,10 +235,71 @@ export class CrucibleAgentRuntime {
216235 await this . loadJejuPlugin ( )
217236 }
218237
238+ // Initialize memory persistence if configured
239+ if (
240+ this . config . enableMemoryPersistence !== false &&
241+ this . config . onChainAgentId &&
242+ this . config . characterCid &&
243+ this . config . stateCid &&
244+ this . jejuService
245+ ) {
246+ try {
247+ const { loadDeployedContracts } = await import ( '@jejunetwork/config' )
248+ const network =
249+ this . config . network ?? ( getCurrentNetwork ( ) as NetworkType )
250+ const contracts = loadDeployedContracts ( network )
251+ const { createStorage } = await import ( './storage' )
252+ const { createKMSSigner } = await import ( './kms-signer' )
253+
254+ if ( contracts . identityRegistry ) {
255+ // Create storage service
256+ const storage = createStorage ( {
257+ apiUrl : getDWSEndpoint ( ) ,
258+ ipfsGateway : getDWSEndpoint ( ) . replace ( '/api' , '/ipfs' ) ,
259+ } )
260+
261+ // Create signer from Jeju service wallet client
262+ const signer = createWalletSigner (
263+ this . jejuService . sdk . walletClient ,
264+ this . jejuService . sdk . publicClient ,
265+ )
266+
267+ this . memoryPersistence = createMemoryPersistence ( {
268+ agentId : this . config . onChainAgentId ,
269+ characterCid : this . config . characterCid ,
270+ stateCid : this . config . stateCid ,
271+ identityRegistry : contracts . identityRegistry as `0x${string } `,
272+ storage,
273+ publicClient : this . jejuService . sdk . publicClient ,
274+ signer,
275+ snapshotIntervalMs : this . config . memorySnapshotIntervalMs ?? 60000 ,
276+ enablePeriodicSnapshots : true ,
277+ enableEventCommits : true ,
278+ logger : this . log ,
279+ } )
280+
281+ await this . memoryPersistence . start ( )
282+ this . log . info ( 'Memory persistence started' , {
283+ agentId : this . config . onChainAgentId . toString ( ) ,
284+ snapshotInterval : this . config . memorySnapshotIntervalMs ?? 60000 ,
285+ } )
286+ } else {
287+ this . log . warn (
288+ 'IdentityRegistry not deployed - memory persistence disabled' ,
289+ )
290+ }
291+ } catch ( err ) {
292+ this . log . warn ( 'Failed to initialize memory persistence' , {
293+ error : err instanceof Error ? err . message : String ( err ) ,
294+ } )
295+ }
296+ }
297+
219298 this . log . info ( 'Agent runtime initialized' , {
220299 agentId : this . config . agentId ,
221300 characterName : this . config . character . name ,
222301 actions : jejuActions . length ,
302+ memoryPersistence : this . memoryPersistence !== null ,
223303 } )
224304
225305 this . initialized = true
@@ -438,6 +518,16 @@ export class CrucibleAgentRuntime {
438518 textLength : userText . length ,
439519 } )
440520
521+ // Record user message for persistence
522+ if ( this . memoryPersistence ) {
523+ this . memoryPersistence . recordMessage ( {
524+ roomId : message . roomId ,
525+ userId : message . userId ,
526+ content : userText ,
527+ role : 'user' ,
528+ } )
529+ }
530+
441531 // Determine model based on network and character preferences
442532 const network = getCurrentNetwork ( )
443533 const modelPrefs = this . config . character . modelPreferences
@@ -475,6 +565,21 @@ export class CrucibleAgentRuntime {
475565 ? `${ cleanText } \n\n${ actionResultText } `
476566 : cleanText
477567
568+ // Record response and trigger event-based commit for action
569+ if ( this . memoryPersistence ) {
570+ this . memoryPersistence . recordMessage ( {
571+ roomId : message . roomId ,
572+ userId : this . config . agentId ,
573+ content : combinedText ,
574+ role : 'assistant' ,
575+ action,
576+ } )
577+ // Trigger immediate commit on action execution
578+ this . memoryPersistence . onActionExecuted ( action , execResult . success ) . catch ( err => {
579+ this . log . warn ( 'Failed to commit after action' , { error : String ( err ) } )
580+ } )
581+ }
582+
478583 return {
479584 text : combinedText ,
480585 action,
@@ -494,6 +599,17 @@ export class CrucibleAgentRuntime {
494599 }
495600 }
496601
602+ // Record response message for persistence (no action case)
603+ if ( this . memoryPersistence ) {
604+ this . memoryPersistence . recordMessage ( {
605+ roomId : message . roomId ,
606+ userId : this . config . agentId ,
607+ content : cleanText ,
608+ role : 'assistant' ,
609+ action,
610+ } )
611+ }
612+
497613 return {
498614 text : cleanText ,
499615 action,
@@ -1871,6 +1987,66 @@ _Generated by daily-digest agent at ${now.toISOString()}_`
18711987 return { success : false , error : errorMsg }
18721988 }
18731989 }
1990+
1991+ // ============================================
1992+ // Memory Persistence Methods
1993+ // ============================================
1994+
1995+ /**
1996+ * Get memory persistence stats
1997+ */
1998+ getMemoryPersistenceStats ( ) : {
1999+ enabled : boolean
2000+ pendingMessages : number
2001+ totalCommits : number
2002+ totalMessagesCommitted : number
2003+ lastCommitTime : number
2004+ currentStateCid : string | null
2005+ } | null {
2006+ if ( ! this . memoryPersistence ) {
2007+ return { enabled : false , pendingMessages : 0 , totalCommits : 0 , totalMessagesCommitted : 0 , lastCommitTime : 0 , currentStateCid : null }
2008+ }
2009+ const stats = this . memoryPersistence . getStats ( )
2010+ return {
2011+ enabled : true ,
2012+ ...stats ,
2013+ }
2014+ }
2015+
2016+ /**
2017+ * Manually trigger a memory commit
2018+ */
2019+ async commitMemory ( ) : Promise < CommitResult | null > {
2020+ if ( ! this . memoryPersistence ) {
2021+ return null
2022+ }
2023+ return this . memoryPersistence . commit ( )
2024+ }
2025+
2026+ /**
2027+ * Notify session end (triggers commit)
2028+ */
2029+ async onSessionEnd ( roomId : string ) : Promise < CommitResult | null > {
2030+ if ( ! this . memoryPersistence ) {
2031+ return null
2032+ }
2033+ return this . memoryPersistence . onSessionEnd ( roomId )
2034+ }
2035+
2036+ /**
2037+ * Shutdown the runtime (commits pending memories)
2038+ */
2039+ async shutdown ( ) : Promise < CommitResult | null > {
2040+ let result : CommitResult | null = null
2041+ if ( this . memoryPersistence ) {
2042+ result = await this . memoryPersistence . stop ( )
2043+ this . log . info ( 'Memory persistence stopped' , {
2044+ committed : result ?. messagesCommitted ?? 0 ,
2045+ } )
2046+ }
2047+ this . initialized = false
2048+ return result
2049+ }
18742050}
18752051
18762052/**
@@ -1912,8 +2088,33 @@ export class CrucibleRuntimeManager {
19122088 }
19132089
19142090 async shutdown ( ) : Promise < void > {
2091+ // Shutdown all runtimes and commit pending memories
2092+ const shutdownPromises = Array . from ( this . runtimes . values ( ) ) . map (
2093+ async ( runtime ) => {
2094+ try {
2095+ const result = await runtime . shutdown ( )
2096+ return { agentId : runtime . getAgentId ( ) , result }
2097+ } catch ( err ) {
2098+ this . log . error ( 'Failed to shutdown runtime' , {
2099+ agentId : runtime . getAgentId ( ) ,
2100+ error : String ( err ) ,
2101+ } )
2102+ return { agentId : runtime . getAgentId ( ) , result : null }
2103+ }
2104+ } ,
2105+ )
2106+
2107+ const results = await Promise . all ( shutdownPromises )
2108+ const totalCommitted = results . reduce (
2109+ ( sum , r ) => sum + ( r . result ?. messagesCommitted ?? 0 ) ,
2110+ 0 ,
2111+ )
2112+
19152113 this . runtimes . clear ( )
1916- this . log . info ( 'All runtimes shut down' )
2114+ this . log . info ( 'All runtimes shut down' , {
2115+ runtimes : results . length ,
2116+ totalMessagesCommitted : totalCommitted ,
2117+ } )
19172118 }
19182119}
19192120
0 commit comments