Smart Contract
AgentRegistry
A.91d0a5b7c9832a8b.AgentRegistry
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