Smart Contract

AgentRegistry

A.91d0a5b7c9832a8b.AgentRegistry

Valid From

143,525,711

Deployed

1d ago
Feb 27, 2026, 12:06:29 AM UTC

Dependents

0 imports
1// AgentRegistry.cdc
2// Core contract for registering and managing AI agents on Flow.
3// Each Flow account owns their Agents as Resources in an AgentCollection.
4// Supports multiple agents per account, sub-agent spawning with TTL,
5// and default agent selection.
6// Private inference config (model, provider, encrypted API key hash) lives in the owner's storage.
7
8access(all) contract AgentRegistry {
9
10    // -----------------------------------------------------------------------
11    // Events
12    // -----------------------------------------------------------------------
13    access(all) event AgentCreated(id: UInt64, owner: Address, name: String)
14    access(all) event AgentUpdated(id: UInt64, owner: Address)
15    access(all) event AgentDestroyed(id: UInt64, owner: Address)
16    access(all) event AgentPaused(id: UInt64)
17    access(all) event AgentResumed(id: UInt64)
18    access(all) event SubAgentSpawned(parentId: UInt64, childId: UInt64, owner: Address, name: String)
19    access(all) event SubAgentExpired(id: UInt64, parentId: UInt64, owner: Address)
20    access(all) event DefaultAgentChanged(owner: Address, agentId: UInt64)
21
22    // -----------------------------------------------------------------------
23    // Paths
24    // -----------------------------------------------------------------------
25    access(all) let AgentStoragePath: StoragePath      // Legacy single-agent path
26    access(all) let AgentPublicPath: PublicPath         // Legacy public path
27    access(all) let AgentCollectionStoragePath: StoragePath
28    access(all) let AgentCollectionPublicPath: PublicPath
29
30    // -----------------------------------------------------------------------
31    // State
32    // -----------------------------------------------------------------------
33    access(all) var totalAgents: UInt64
34
35    // -----------------------------------------------------------------------
36    // Entitlements — fine-grained access control
37    // -----------------------------------------------------------------------
38    access(all) entitlement Configure
39    access(all) entitlement Execute
40    access(all) entitlement Admin
41    access(all) entitlement Manage   // For AgentCollection operations
42
43    // -----------------------------------------------------------------------
44    // SubAgentInfo — metadata for agents spawned by other agents
45    // -----------------------------------------------------------------------
46    access(all) struct SubAgentInfo {
47        access(all) let parentAgentId: UInt64
48        access(all) let createdAt: UFix64
49        access(all) let expiresAt: UFix64?   // nil = permanent sub-agent
50        access(all) let inheritedConfig: Bool
51
52        init(
53            parentAgentId: UInt64,
54            createdAt: UFix64,
55            expiresAt: UFix64?,
56            inheritedConfig: Bool
57        ) {
58            self.parentAgentId = parentAgentId
59            self.createdAt = createdAt
60            self.expiresAt = expiresAt
61            self.inheritedConfig = inheritedConfig
62        }
63
64        access(all) fun isExpired(): Bool {
65            if let expiry = self.expiresAt {
66                return getCurrentBlock().timestamp >= expiry
67            }
68            return false
69        }
70    }
71
72    // -----------------------------------------------------------------------
73    // InferenceConfig — private per-account model/provider settings
74    // -----------------------------------------------------------------------
75    access(all) struct InferenceConfig {
76        access(all) let provider: String       // "anthropic", "openai", "ollama", etc.
77        access(all) let model: String          // "claude-sonnet-4-5-20250929", etc.
78        access(all) let apiKeyHash: String     // SHA-256 hash of encrypted API key (actual key stored off-chain)
79        access(all) let maxTokens: UInt64
80        access(all) let temperature: UFix64    // 0.0 to 2.0
81        access(all) let systemPrompt: String
82
83        init(
84            provider: String,
85            model: String,
86            apiKeyHash: String,
87            maxTokens: UInt64,
88            temperature: UFix64,
89            systemPrompt: String
90        ) {
91            self.provider = provider
92            self.model = model
93            self.apiKeyHash = apiKeyHash
94            self.maxTokens = maxTokens
95            self.temperature = temperature
96            self.systemPrompt = systemPrompt
97        }
98    }
99
100    // -----------------------------------------------------------------------
101    // SecurityPolicy — defense-in-depth settings per agent
102    // -----------------------------------------------------------------------
103    access(all) struct SecurityPolicy {
104        access(all) let autonomyLevel: UInt8   // 0=readonly, 1=supervised, 2=full
105        access(all) let maxActionsPerHour: UInt64
106        access(all) let maxCostPerDay: UFix64  // in FLOW
107        access(all) let allowedTools: [String]
108        access(all) let deniedTools: [String]
109
110        init(
111            autonomyLevel: UInt8,
112            maxActionsPerHour: UInt64,
113            maxCostPerDay: UFix64,
114            allowedTools: [String],
115            deniedTools: [String]
116        ) {
117            pre {
118                autonomyLevel <= 2: "Autonomy level must be 0, 1, or 2"
119            }
120            self.autonomyLevel = autonomyLevel
121            self.maxActionsPerHour = maxActionsPerHour
122            self.maxCostPerDay = maxCostPerDay
123            self.allowedTools = allowedTools
124            self.deniedTools = deniedTools
125        }
126    }
127
128    // -----------------------------------------------------------------------
129    // AgentPublicInfo — read-only view for public capabilities
130    // -----------------------------------------------------------------------
131    access(all) struct AgentPublicInfo {
132        access(all) let id: UInt64
133        access(all) let name: String
134        access(all) let description: String
135        access(all) let owner: Address
136        access(all) let createdAt: UFix64
137        access(all) let isActive: Bool
138        access(all) let totalSessions: UInt64
139        access(all) let totalInferences: UInt64
140
141        init(
142            id: UInt64,
143            name: String,
144            description: String,
145            owner: Address,
146            createdAt: UFix64,
147            isActive: Bool,
148            totalSessions: UInt64,
149            totalInferences: UInt64
150        ) {
151            self.id = id
152            self.name = name
153            self.description = description
154            self.owner = owner
155            self.createdAt = createdAt
156            self.isActive = isActive
157            self.totalSessions = totalSessions
158            self.totalInferences = totalInferences
159        }
160    }
161
162    // -----------------------------------------------------------------------
163    // Agent Resource — the core owned entity
164    // -----------------------------------------------------------------------
165    access(all) resource Agent {
166        access(all) let id: UInt64
167        access(all) var name: String
168        access(all) var description: String
169        access(all) let createdAt: UFix64
170        access(all) var isActive: Bool
171        access(all) var totalSessions: UInt64
172        access(all) var totalInferences: UInt64
173
174        // Private: only owner can read/write
175        access(self) var inferenceConfig: InferenceConfig
176        access(self) var securityPolicy: SecurityPolicy
177        access(self) var actionCountThisHour: UInt64
178        access(self) var costToday: UFix64
179        access(self) var lastHourReset: UFix64
180        access(self) var lastDayReset: UFix64
181
182        init(
183            id: UInt64,
184            name: String,
185            description: String,
186            inferenceConfig: InferenceConfig,
187            securityPolicy: SecurityPolicy
188        ) {
189            self.id = id
190            self.name = name
191            self.description = description
192            self.createdAt = getCurrentBlock().timestamp
193            self.isActive = true
194            self.totalSessions = 0
195            self.totalInferences = 0
196            self.inferenceConfig = inferenceConfig
197            self.securityPolicy = securityPolicy
198            self.actionCountThisHour = 0
199            self.costToday = 0.0
200            self.lastHourReset = getCurrentBlock().timestamp
201            self.lastDayReset = getCurrentBlock().timestamp
202        }
203
204        // --- Public read-only view ---
205        access(all) fun getPublicInfo(): AgentPublicInfo {
206            return AgentPublicInfo(
207                id: self.id,
208                name: self.name,
209                description: self.description,
210                owner: self.owner!.address,
211                createdAt: self.createdAt,
212                isActive: self.isActive,
213                totalSessions: self.totalSessions,
214                totalInferences: self.totalInferences
215            )
216        }
217
218        // --- Configure entitlement: update settings ---
219        access(Configure) fun updateInferenceConfig(_ config: InferenceConfig) {
220            self.inferenceConfig = config
221            emit AgentUpdated(id: self.id, owner: self.owner!.address)
222        }
223
224        access(Configure) fun updateSecurityPolicy(_ policy: SecurityPolicy) {
225            self.securityPolicy = policy
226            emit AgentUpdated(id: self.id, owner: self.owner!.address)
227        }
228
229        access(Configure) fun updateName(_ name: String) {
230            self.name = name
231        }
232
233        access(Configure) fun updateDescription(_ description: String) {
234            self.description = description
235        }
236
237        // --- Execute entitlement: run agent actions ---
238        access(Execute) fun getInferenceConfig(): InferenceConfig {
239            return self.inferenceConfig
240        }
241
242        access(Execute) fun getSecurityPolicy(): SecurityPolicy {
243            return self.securityPolicy
244        }
245
246        access(Execute) fun recordInference() {
247            self.totalInferences = self.totalInferences + 1
248            self.actionCountThisHour = self.actionCountThisHour + 1
249        }
250
251        access(Execute) fun recordSession() {
252            self.totalSessions = self.totalSessions + 1
253        }
254
255        access(Execute) fun recordCost(amount: UFix64) {
256            self.costToday = self.costToday + amount
257        }
258
259        access(Execute) fun checkRateLimits(): Bool {
260            let now = getCurrentBlock().timestamp
261            // Reset hourly counter
262            if now - self.lastHourReset > 3600.0 {
263                self.actionCountThisHour = 0
264                self.lastHourReset = now
265            }
266            // Reset daily counter
267            if now - self.lastDayReset > 86400.0 {
268                self.costToday = 0.0
269                self.lastDayReset = now
270            }
271            let policy = self.securityPolicy
272            if self.actionCountThisHour >= policy.maxActionsPerHour {
273                return false
274            }
275            if self.costToday >= policy.maxCostPerDay {
276                return false
277            }
278            return true
279        }
280
281        // --- Admin entitlement: pause/resume ---
282        access(Admin) fun pause() {
283            self.isActive = false
284            emit AgentPaused(id: self.id)
285        }
286
287        access(Admin) fun resume() {
288            self.isActive = true
289            emit AgentResumed(id: self.id)
290        }
291    }
292
293    // -----------------------------------------------------------------------
294    // Public interface for reading agent info
295    // -----------------------------------------------------------------------
296    access(all) resource interface AgentPublicView {
297        access(all) fun getPublicInfo(): AgentPublicInfo
298    }
299
300    // -----------------------------------------------------------------------
301    // AgentCollection — holds multiple agents per account
302    // -----------------------------------------------------------------------
303    access(all) resource AgentCollection {
304        access(self) let agents: @{UInt64: Agent}
305        access(self) var defaultAgentId: UInt64?
306        access(self) let subAgentInfo: {UInt64: SubAgentInfo}
307
308        init() {
309            self.agents <- {}
310            self.defaultAgentId = nil
311            self.subAgentInfo = {}
312        }
313
314        // --- Manage entitlement: add/remove agents ---
315
316        access(Manage) fun addAgent(_ agent: @Agent) {
317            let id = agent.id
318            let old <- self.agents[id] <- agent
319            destroy old
320
321            // If this is the first agent, make it the default
322            if self.defaultAgentId == nil {
323                self.defaultAgentId = id
324            }
325        }
326
327        access(Manage) fun createAgent(
328            name: String,
329            description: String,
330            ownerAddress: Address,
331            inferenceConfig: InferenceConfig,
332            securityPolicy: SecurityPolicy
333        ): UInt64 {
334            let agent <- AgentRegistry.createAgent(
335                name: name,
336                description: description,
337                ownerAddress: ownerAddress,
338                inferenceConfig: inferenceConfig,
339                securityPolicy: securityPolicy
340            )
341            let id = agent.id
342            self.addAgent(<- agent)
343            return id
344        }
345
346        access(Manage) fun removeAgent(id: UInt64): @Agent {
347            let agent <- self.agents.remove(key: id)
348                ?? panic("Agent not found in collection")
349
350            // If we removed the default, pick another
351            if self.defaultAgentId == id {
352                let remaining = self.agents.keys
353                self.defaultAgentId = remaining.length > 0 ? remaining[0] : nil
354            }
355
356            // Clean up sub-agent info
357            self.subAgentInfo.remove(key: id)
358
359            return <- agent
360        }
361
362        access(Manage) fun setDefault(agentId: UInt64) {
363            pre {
364                self.agents[agentId] != nil: "Agent not in collection"
365            }
366            self.defaultAgentId = agentId
367            emit DefaultAgentChanged(owner: self.owner!.address, agentId: agentId)
368        }
369
370        // --- Sub-agent spawning ---
371
372        access(Manage) fun spawnSubAgent(
373            parentAgentId: UInt64,
374            name: String,
375            description: String,
376            ownerAddress: Address,
377            ttlSeconds: UFix64?,
378            inheritConfig: Bool
379        ): UInt64 {
380            pre {
381                self.agents[parentAgentId] != nil: "Parent agent not found"
382            }
383
384            // Build config: inherit from parent or use defaults
385            var inferenceConfig: InferenceConfig? = nil
386            var securityPolicy: SecurityPolicy? = nil
387
388            if inheritConfig {
389                let parentRef = (&self.agents[parentAgentId] as auth(Execute) &Agent?)!
390                inferenceConfig = parentRef.getInferenceConfig()
391                securityPolicy = parentRef.getSecurityPolicy()
392            }
393
394            let config = inferenceConfig ?? InferenceConfig(
395                provider: "venice",
396                model: "claude-sonnet-4-6",
397                apiKeyHash: "",
398                maxTokens: 4096,
399                temperature: 0.70000000,
400                systemPrompt: "You are a sub-agent of ".concat(name)
401            )
402            let policy = securityPolicy ?? SecurityPolicy(
403                autonomyLevel: 1,
404                maxActionsPerHour: 50,
405                maxCostPerDay: 1.0,
406                allowedTools: ["memory_store", "memory_recall"],
407                deniedTools: ["shell_exec"]
408            )
409
410            let agent <- AgentRegistry.createAgent(
411                name: name,
412                description: description,
413                ownerAddress: ownerAddress,
414                inferenceConfig: config,
415                securityPolicy: policy
416            )
417            let childId = agent.id
418
419            // Calculate expiry
420            var expiresAt: UFix64? = nil
421            if let ttl = ttlSeconds {
422                expiresAt = getCurrentBlock().timestamp + ttl
423            }
424
425            // Store sub-agent metadata
426            self.subAgentInfo[childId] = SubAgentInfo(
427                parentAgentId: parentAgentId,
428                createdAt: getCurrentBlock().timestamp,
429                expiresAt: expiresAt,
430                inheritedConfig: inheritConfig
431            )
432
433            self.addAgent(<- agent)
434
435            emit SubAgentSpawned(
436                parentId: parentAgentId,
437                childId: childId,
438                owner: ownerAddress,
439                name: name
440            )
441
442            return childId
443        }
444
445        // --- Clean up expired sub-agents ---
446
447        access(Manage) fun cleanupExpiredSubAgents(): [UInt64] {
448            let expired: [UInt64] = []
449            for id in self.subAgentInfo.keys {
450                if let info = self.subAgentInfo[id] {
451                    if info.isExpired() {
452                        expired.append(id)
453                    }
454                }
455            }
456            for id in expired {
457                let agent <- self.removeAgent(id: id)
458                emit SubAgentExpired(
459                    id: id,
460                    parentId: self.subAgentInfo[id]?.parentAgentId ?? 0,
461                    owner: self.owner!.address
462                )
463                destroy agent
464            }
465            return expired
466        }
467
468        // --- Read access ---
469
470        access(all) fun borrowAgent(id: UInt64): &Agent? {
471            return &self.agents[id]
472        }
473
474        access(Manage) fun borrowAgentManaged(id: UInt64): auth(Configure, Execute, Admin) &Agent? {
475            return &self.agents[id]
476        }
477
478        access(all) fun getAgentIds(): [UInt64] {
479            return self.agents.keys
480        }
481
482        access(all) fun getAgentCount(): Int {
483            return self.agents.keys.length
484        }
485
486        access(all) fun getDefaultAgentId(): UInt64? {
487            return self.defaultAgentId
488        }
489
490        access(all) fun getSubAgentInfo(agentId: UInt64): SubAgentInfo? {
491            return self.subAgentInfo[agentId]
492        }
493
494        access(all) fun isSubAgent(agentId: UInt64): Bool {
495            return self.subAgentInfo[agentId] != nil
496        }
497
498        access(all) fun getSubAgents(parentId: UInt64): [UInt64] {
499            let children: [UInt64] = []
500            for id in self.subAgentInfo.keys {
501                if let info = self.subAgentInfo[id] {
502                    if info.parentAgentId == parentId {
503                        children.append(id)
504                    }
505                }
506            }
507            return children
508        }
509
510        access(all) fun getAllAgentInfo(): [AgentPublicInfo] {
511            let infos: [AgentPublicInfo] = []
512            for id in self.agents.keys {
513                if let agent = &self.agents[id] as &Agent? {
514                    infos.append(agent.getPublicInfo())
515                }
516            }
517            return infos
518        }
519    }
520
521    // -----------------------------------------------------------------------
522    // Create Agent — called by any Flow account to register their agent
523    // -----------------------------------------------------------------------
524    access(all) fun createAgent(
525        name: String,
526        description: String,
527        ownerAddress: Address,
528        inferenceConfig: InferenceConfig,
529        securityPolicy: SecurityPolicy
530    ): @Agent {
531        self.totalAgents = self.totalAgents + 1
532        let agent <- create Agent(
533            id: self.totalAgents,
534            name: name,
535            description: description,
536            inferenceConfig: inferenceConfig,
537            securityPolicy: securityPolicy
538        )
539        emit AgentCreated(id: agent.id, owner: ownerAddress, name: name)
540        return <- agent
541    }
542
543    // -----------------------------------------------------------------------
544    // Create AgentCollection — for multi-agent accounts
545    // -----------------------------------------------------------------------
546    access(all) fun createAgentCollection(): @AgentCollection {
547        return <- create AgentCollection()
548    }
549
550    // -----------------------------------------------------------------------
551    // Init
552    // -----------------------------------------------------------------------
553    init() {
554        self.totalAgents = 0
555        self.AgentStoragePath = /storage/FlowClawAgent
556        self.AgentPublicPath = /public/FlowClawAgent
557        self.AgentCollectionStoragePath = /storage/FlowClawAgentCollection
558        self.AgentCollectionPublicPath = /public/FlowClawAgentCollection
559    }
560}
561