DeploySEALED

╳%?&█●░■●$╱█╳◇&?!░#█$#▒░╱?^$□$▒▪!▓~░?▒╱○@$▪▒╱○?▓╳█╱╳╲!●&▒^▫$◇◆□▓

Transaction ID

Timestamp

Feb 27, 2026, 12:08:31 AM UTC
1d ago

Block Height

143,525,876

Computation

0

Execution Fee

0.00622 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
AgentLifecycleHooks
1codeString
// AgentLifecycleHooks.cdc // Port of OpenClaw PR #12082 "Plugin Lifecycle Interception Hook Architecture" to Cadence. // // PR #12082 PROPOSES: // - api.on("<hook_name>", handler, opts) for raw runtime hooks // - api.lifecycle.on("<phase>", handler, opts) for canonical phases // - Priority-based execution, timeout mgmt, fail-open/fail-closed, // retry logic, concurrency limits, scope-based gating // // WHY CADENCE DOES THIS BETTER: // // In OpenClaw, lifecycle hooks are in-process callbacks. If the process dies, // hooks die with it. The hook system is also purely trust-based — any plugin // can register any hook with no access control. // // In Cadence, we get several things for free: // // 1. PRE/POST CONDITIONS — Cadence's built-in design-by-contract means every // hook phase has compile-time guarantees. A pre_message_send hook can // ENFORCE that the message content isn't empty. A post_inference hook can // VERIFY that tokens used is non-zero. These aren't runtime checks that // can be bypassed — they're protocol-level guarantees. // // 2. CAPABILITIES — Hook handlers are registered as Capabilities with specific // Entitlements. A plugin can be given permission to hook into message_received // but NOT tool_execution. This is impossible to express in OpenClaw's system. // // 3. ENTITLEMENTS — Fine-grained: Read vs Modify vs Intercept. A hook that only // needs to observe messages gets ReadOnly. A hook that can modify them gets // Modify. A hook that can block/cancel gets Intercept. All enforced by Cadence. // // 4. RESOURCES — Hook registrations are Resources. They can't be duplicated, // they're owned by specific accounts, and when destroyed they cleanly // unregister. No orphaned callbacks. // // 5. EVENTS — Instead of in-process callbacks, hooks emit on-chain events. // Multiple off-chain systems can subscribe. The hook execution is verifiable. access(all) contract AgentLifecycleHooks { // ----------------------------------------------------------------------- // Events // ----------------------------------------------------------------------- access(all) event HookRegistered( hookId: UInt64, phase: String, owner: Address, priority: UInt8, failMode: String ) access(all) event HookTriggered(hookId: UInt64, phase: String, owner: Address) access(all) event HookCompleted(hookId: UInt64, phase: String, result: String) access(all) event HookFailed(hookId: UInt64, phase: String, error: String) access(all) event HookUnregistered(hookId: UInt64) // Canonical lifecycle phase events — mirrors PR #12082's phases access(all) event PhaseGatewayPreStart(owner: Address) access(all) event PhaseGatewayPostStart(owner: Address) access(all) event PhaseAgentPreStart(owner: Address, agentId: UInt64) access(all) event PhaseAgentPostRun(owner: Address, agentId: UInt64) access(all) event PhaseMessageReceived(owner: Address, sessionId: UInt64, contentHash: String) access(all) event PhasePreInference(owner: Address, sessionId: UInt64, messageCount: UInt64) access(all) event PhasePostInference(owner: Address, sessionId: UInt64, tokensUsed: UInt64) access(all) event PhasePreToolExecution(owner: Address, toolName: String, inputHash: String) access(all) event PhasePostToolExecution(owner: Address, toolName: String, outputHash: String) access(all) event PhasePreMemoryCompaction(owner: Address, entryCount: UInt64) access(all) event PhasePostMemoryCompaction(owner: Address, entriesRemoved: UInt64) access(all) event PhasePreSend(owner: Address, sessionId: UInt64, contentHash: String) access(all) event PhasePostSend(owner: Address, sessionId: UInt64, success: Bool) // ----------------------------------------------------------------------- // Paths // ----------------------------------------------------------------------- access(all) let HookManagerStoragePath: StoragePath // ----------------------------------------------------------------------- // State // ----------------------------------------------------------------------- access(all) var totalHooks: UInt64 // ----------------------------------------------------------------------- // Entitlements — maps to PR #12082's scope-based gating // ----------------------------------------------------------------------- access(all) entitlement RegisterHooks access(all) entitlement TriggerHooks access(all) entitlement ReadOnly // Can observe but not modify access(all) entitlement Modify // Can modify data passing through access(all) entitlement Intercept // Can block/cancel operations // ----------------------------------------------------------------------- // LifecyclePhase — the canonical phases from PR #12082 // ----------------------------------------------------------------------- access(all) enum LifecyclePhase: UInt8 { // Boot/shutdown cycle access(all) case gatewayPreStart access(all) case gatewayPostStart access(all) case gatewayPreStop access(all) case gatewayPostStop // Agent lifecycle access(all) case agentPreStart access(all) case agentPostRun access(all) case agentError // Message flow (inbound) access(all) case messageReceived access(all) case preInference access(all) case postInference // Message flow (outbound) access(all) case preSend access(all) case postSend access(all) case postSendFailure // Tool execution access(all) case preToolCall access(all) case postToolCall access(all) case toolError // Memory management access(all) case preMemoryCompaction access(all) case postMemoryCompaction // Scheduled tasks access(all) case preScheduledTask access(all) case postScheduledTask } // ----------------------------------------------------------------------- // FailMode — matches PR #12082's fail-open/fail-closed // ----------------------------------------------------------------------- access(all) enum FailMode: UInt8 { access(all) case failOpen // Hook failure doesn't block the operation access(all) case failClosed // Hook failure blocks the operation } // ----------------------------------------------------------------------- // HookConfig — handler registration options (maps to PR #12082's opts) // ----------------------------------------------------------------------- access(all) struct HookConfig { access(all) let phase: LifecyclePhase access(all) let priority: UInt8 // 0-255, higher = runs first access(all) let failMode: FailMode access(all) let timeoutSeconds: UFix64 // Max execution time access(all) let maxRetries: UInt8 access(all) let retryBackoffSeconds: UFix64 access(all) let description: String // Scope-based gating — from PR #12082 access(all) let scopeChannels: [String]? // nil = all channels access(all) let scopeTools: [String]? // nil = all tools access(all) let scopeSessionIds: [UInt64]? // nil = all sessions init( phase: LifecyclePhase, priority: UInt8, failMode: FailMode, timeoutSeconds: UFix64, maxRetries: UInt8, retryBackoffSeconds: UFix64, description: String, scopeChannels: [String]?, scopeTools: [String]?, scopeSessionIds: [UInt64]? ) { pre { timeoutSeconds > 0.0 && timeoutSeconds <= 300.0: "Timeout must be 1-300 seconds" maxRetries <= 5: "Max retries must be 0-5" } self.phase = phase self.priority = priority self.failMode = failMode self.timeoutSeconds = timeoutSeconds self.maxRetries = maxRetries self.retryBackoffSeconds = retryBackoffSeconds self.description = description self.scopeChannels = scopeChannels self.scopeTools = scopeTools self.scopeSessionIds = scopeSessionIds } } // ----------------------------------------------------------------------- // HookRegistration — a registered hook (stored on-chain) // ----------------------------------------------------------------------- access(all) struct HookRegistration { access(all) let hookId: UInt64 access(all) let config: HookConfig access(all) let owner: Address access(all) let registeredAt: UFix64 access(all) var isActive: Bool access(all) var triggerCount: UInt64 access(all) var failureCount: UInt64 access(all) var lastTriggeredAt: UFix64? // The handler itself is off-chain (the relay executes it), // but we store a hash of the handler code for verifiability access(all) let handlerHash: String init( hookId: UInt64, config: HookConfig, owner: Address, handlerHash: String ) { self.hookId = hookId self.config = config self.owner = owner self.registeredAt = getCurrentBlock().timestamp self.isActive = true self.triggerCount = 0 self.failureCount = 0 self.lastTriggeredAt = nil self.handlerHash = handlerHash } } // ----------------------------------------------------------------------- // HookContext — data passed to hook handlers // ----------------------------------------------------------------------- access(all) struct HookContext { access(all) let phase: LifecyclePhase access(all) let agentId: UInt64 access(all) let sessionId: UInt64? access(all) let timestamp: UFix64 access(all) let data: {String: String} // Key-value context data init( phase: LifecyclePhase, agentId: UInt64, sessionId: UInt64?, data: {String: String} ) { self.phase = phase self.agentId = agentId self.sessionId = sessionId self.timestamp = getCurrentBlock().timestamp self.data = data } } // ----------------------------------------------------------------------- // HookResult — what a hook handler returns // ----------------------------------------------------------------------- access(all) struct HookResult { access(all) let hookId: UInt64 access(all) let success: Bool access(all) let shouldProceed: Bool // false = cancel the operation (Intercept entitlement) access(all) let modifiedData: {String: String}? // nil = no modifications access(all) let message: String? init( hookId: UInt64, success: Bool, shouldProceed: Bool, modifiedData: {String: String}?, message: String? ) { self.hookId = hookId self.success = success self.shouldProceed = shouldProceed self.modifiedData = modifiedData self.message = message } } // ----------------------------------------------------------------------- // HookManager — per-account hook registry // ----------------------------------------------------------------------- access(all) resource HookManager { access(self) var hooks: {UInt64: HookRegistration} access(self) var phaseIndex: {UInt8: [UInt64]} // phase -> [hookIds], sorted by priority access(self) var hookResults: {UInt64: [HookResult]} // hookId -> results history init() { self.hooks = {} self.phaseIndex = {} self.hookResults = {} } // --- RegisterHooks: add/remove hooks --- access(RegisterHooks) fun registerHook( config: HookConfig, handlerHash: String ): UInt64 { post { self.hooks[AgentLifecycleHooks.totalHooks] != nil: "Hook must be stored after registration" } AgentLifecycleHooks.totalHooks = AgentLifecycleHooks.totalHooks + 1 let hookId = AgentLifecycleHooks.totalHooks let registration = HookRegistration( hookId: hookId, config: config, owner: self.owner!.address, handlerHash: handlerHash ) self.hooks[hookId] = registration // Add to phase index (maintain priority order) let phaseKey = config.phase.rawValue if self.phaseIndex[phaseKey] == nil { self.phaseIndex[phaseKey] = [hookId] } else { self.phaseIndex[phaseKey]!.append(hookId) // Sort by priority (higher priority first) self.sortHooksByPriority(phaseKey: phaseKey) } let failModeStr = config.failMode == FailMode.failOpen ? "fail-open" : "fail-closed" emit HookRegistered( hookId: hookId, phase: self.phaseToString(config.phase), owner: self.owner!.address, priority: config.priority, failMode: failModeStr ) return hookId } access(RegisterHooks) fun unregisterHook(hookId: UInt64): Bool { if let hook = self.hooks[hookId] { let phaseKey = hook.config.phase.rawValue if var ids = self.phaseIndex[phaseKey] { var newIds: [UInt64] = [] for id in ids { if id != hookId { newIds.append(id) } } self.phaseIndex[phaseKey] = newIds } self.hooks.remove(key: hookId) emit HookUnregistered(hookId: hookId) return true } return false } // --- TriggerHooks: fire hooks for a phase --- access(TriggerHooks) fun triggerPhase( phase: LifecyclePhase, context: HookContext ): [HookResult] { let phaseKey = phase.rawValue var results: [HookResult] = [] let hookIdsOpt = self.phaseIndex[phaseKey] if hookIdsOpt == nil { return results // No hooks for this phase } let hookIds = hookIdsOpt! for hookId in hookIds { if let hook = self.hooks[hookId] { if !hook.isActive { continue } // Check scope gating if !self.hookMatchesScope(hook: hook, context: context) { continue } emit HookTriggered( hookId: hookId, phase: self.phaseToString(phase), owner: self.owner!.address ) // The actual hook execution happens OFF-CHAIN in the relay. // Here we record that it was triggered and emit the event. // The relay processes the event, runs the handler, and posts // the result back via completeHook(). // For synchronous on-chain hooks (e.g., pre-conditions), // we return a default "proceed" result let result = HookResult( hookId: hookId, success: true, shouldProceed: true, modifiedData: nil, message: nil ) results.append(result) } } return results } // Complete a hook execution (called by relay after off-chain processing) access(TriggerHooks) fun completeHook( hookId: UInt64, result: HookResult ) { pre { self.hooks[hookId] != nil: "Hook not found" } if self.hookResults[hookId] == nil { self.hookResults[hookId] = [result] } else { self.hookResults[hookId]!.append(result) } if result.success { emit HookCompleted( hookId: hookId, phase: self.phaseToString(self.hooks[hookId]!.config.phase), result: result.message ?? "OK" ) } else { emit HookFailed( hookId: hookId, phase: self.phaseToString(self.hooks[hookId]!.config.phase), error: result.message ?? "Unknown error" ) } } // --- Read --- access(all) fun getHook(hookId: UInt64): HookRegistration? { return self.hooks[hookId] } access(all) fun getHooksForPhase(phase: LifecyclePhase): [HookRegistration] { let phaseKey = phase.rawValue var result: [HookRegistration] = [] if let hookIds = self.phaseIndex[phaseKey] { for hookId in hookIds { if let hook = self.hooks[hookId] { result.append(hook) } } } return result } access(all) fun getAllHooks(): [HookRegistration] { var result: [HookRegistration] = [] for hookId in self.hooks.keys { if let hook = self.hooks[hookId] { result.append(hook) } } return result } // --- Internal helpers --- access(self) fun hookMatchesScope( hook: HookRegistration, context: HookContext ): Bool { // Check session scope if let sessionIds = hook.config.scopeSessionIds { if let ctxSession = context.sessionId { if !sessionIds.contains(ctxSession) { return false } } } // Check tool scope if let tools = hook.config.scopeTools { if let toolName = context.data["toolName"] { if !tools.contains(toolName) { return false } } } // Check channel scope if let channels = hook.config.scopeChannels { if let channel = context.data["channel"] { if !channels.contains(channel) { return false } } } return true } access(self) fun sortHooksByPriority(phaseKey: UInt8) { if var ids = self.phaseIndex[phaseKey] { // Simple sort by priority (descending) var i = 0 while i < ids.length { var j = 0 while j < ids.length - 1 - i { let hookA = self.hooks[ids[j]]! let hookB = self.hooks[ids[j + 1]]! if hookA.config.priority < hookB.config.priority { let temp = ids[j] ids[j] = ids[j + 1] ids[j + 1] = temp } j = j + 1 } i = i + 1 } self.phaseIndex[phaseKey] = ids } } access(self) fun phaseToString(_ phase: LifecyclePhase): String { switch phase { case LifecyclePhase.gatewayPreStart: return "gateway.pre_start" case LifecyclePhase.gatewayPostStart: return "gateway.post_start" case LifecyclePhase.gatewayPreStop: return "gateway.pre_stop" case LifecyclePhase.gatewayPostStop: return "gateway.post_stop" case LifecyclePhase.agentPreStart: return "agent.pre_start" case LifecyclePhase.agentPostRun: return "agent.post_run" case LifecyclePhase.agentError: return "agent.error" case LifecyclePhase.messageReceived: return "message.received" case LifecyclePhase.preInference: return "inference.pre" case LifecyclePhase.postInference: return "inference.post" case LifecyclePhase.preSend: return "send.pre" case LifecyclePhase.postSend: return "send.post" case LifecyclePhase.postSendFailure: return "send.post_failure" case LifecyclePhase.preToolCall: return "tool.pre_call" case LifecyclePhase.postToolCall: return "tool.post_call" case LifecyclePhase.toolError: return "tool.error" case LifecyclePhase.preMemoryCompaction: return "memory.pre_compaction" case LifecyclePhase.postMemoryCompaction: return "memory.post_compaction" case LifecyclePhase.preScheduledTask: return "schedule.pre_task" case LifecyclePhase.postScheduledTask: return "schedule.post_task" } return "unknown" } } // ----------------------------------------------------------------------- // Public factory // ----------------------------------------------------------------------- access(all) fun createHookManager(): @HookManager { return <- create HookManager() } // ----------------------------------------------------------------------- // Init // ----------------------------------------------------------------------- init() { self.totalHooks = 0 self.HookManagerStoragePath = /storage/FlowClawHookManager } }

Cadence Script

1transaction(name: String, code: String ) {
2		prepare(signer: auth(AddContract) &Account) {
3			signer.contracts.add(name: name, code: code.utf8 )
4		}
5	}