Smart Contract
FlowClaw
A.91d0a5b7c9832a8b.FlowClaw
1// FlowClaw.cdc
2// Main orchestrator contract — the agentic harness on Flow.
3// Ties together AgentRegistry, AgentSession, InferenceOracle, ToolRegistry, and AgentMemory.
4// Each Flow account gets a complete, private agent stack.
5//
6// Architecture:
7// ┌──────────────────────────────────────────────────────┐
8// │ Flow Account (Owner) │
9// │ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
10// │ │ Agent │ │ Sessions │ │ Memory │ │
11// │ │ (Resource) │ │ (Resource) │ │ (Resource)│ │
12// │ │ │ │ │ │ │ │
13// │ │ - config │ │ - history │ │ - k/v store│ │
14// │ │ - security │ │ - messages │ │ - tags │ │
15// │ │ - rate lim │ │ - inference │ │ - search │ │
16// │ └─────────────┘ └──────────────┘ └────────────┘ │
17// │ ┌──────────────┐ ┌──────────────┐ │
18// │ │ Tools │ │ Oracle │ │
19// │ │ (Resource) │ │ Config │ │
20// │ │ │ │ (Resource) │ │
21// │ │ - registry │ │ - relays │ │
22// │ │ - exec log │ │ - dedup │ │
23// │ └──────────────┘ └──────────────┘ │
24// └──────────────────────────────────────────────────────┘
25// │ Events ↓ ↑ Transactions
26// ┌──────────────────────────────────────────────────────┐
27// │ Off-Chain Inference Relay (per-account) │
28// │ - Listens for InferenceRequested events │
29// │ - Calls LLM provider with account's config │
30// │ - Posts results back via completeInference tx │
31// │ - Executes tool calls in sandboxed environment │
32// └──────────────────────────────────────────────────────┘
33
34import AgentRegistry from 0x91d0a5b7c9832a8b
35import AgentSession from 0x91d0a5b7c9832a8b
36import InferenceOracle from 0x91d0a5b7c9832a8b
37import ToolRegistry from 0x91d0a5b7c9832a8b
38import AgentMemory from 0x91d0a5b7c9832a8b
39
40access(all) contract FlowClaw {
41
42 // -----------------------------------------------------------------------
43 // Events
44 // -----------------------------------------------------------------------
45 access(all) event AccountInitialized(owner: Address, agentId: UInt64)
46 access(all) event AgentLoopStarted(agentId: UInt64, sessionId: UInt64, owner: Address)
47 access(all) event AgentLoopCompleted(agentId: UInt64, sessionId: UInt64, turnsUsed: UInt64)
48 access(all) event UserMessageSent(sessionId: UInt64, contentHash: String)
49 access(all) event AgentResponseReceived(sessionId: UInt64, contentHash: String)
50
51 // -----------------------------------------------------------------------
52 // Paths
53 // -----------------------------------------------------------------------
54 access(all) let FlowClawStoragePath: StoragePath
55
56 // -----------------------------------------------------------------------
57 // State
58 // -----------------------------------------------------------------------
59 access(all) var totalAccounts: UInt64
60 access(all) var totalMessages: UInt64
61 access(all) let version: String
62
63 // -----------------------------------------------------------------------
64 // Entitlements
65 // -----------------------------------------------------------------------
66 access(all) entitlement Owner
67 access(all) entitlement Operate
68
69 // -----------------------------------------------------------------------
70 // AgentStack — the complete per-account agent infrastructure
71 // -----------------------------------------------------------------------
72 access(all) resource AgentStack {
73 access(all) let agentId: UInt64
74 access(all) var isInitialized: Bool
75
76 // References to the per-account resources (stored separately)
77 // The AgentStack coordinates between them
78
79 init(agentId: UInt64) {
80 self.agentId = agentId
81 self.isInitialized = true
82 }
83
84 // --- Send a user message and request inference ---
85 // This is the main entry point for the agentic loop
86 access(Operate) fun sendMessage(
87 sessionManager: auth(AgentSession.Manage) &AgentSession.SessionManager,
88 agent: auth(AgentRegistry.Execute) &AgentRegistry.Agent,
89 sessionId: UInt64,
90 content: String,
91 contentHash: String
92 ): UInt64? {
93 // 1. Validate agent is active and within rate limits
94 if !agent.isActive {
95 return nil
96 }
97 if !agent.checkRateLimits() {
98 return nil
99 }
100
101 // 2. Get session
102 let session = sessionManager.borrowSession(sessionId: sessionId)
103 ?? panic("Session not found")
104
105 // 3. Add user message to session
106 session.addMessage(
107 role: "user",
108 content: content,
109 contentHash: contentHash,
110 toolName: nil,
111 toolCallId: nil,
112 tokensEstimate: UInt64(content.length / 4) // rough estimate
113 )
114
115 FlowClaw.totalMessages = FlowClaw.totalMessages + 1
116 emit UserMessageSent(sessionId: sessionId, contentHash: contentHash)
117
118 // 4. Request inference — emits event for relay
119 let requestId = session.requestInference(
120 agentRef: agent,
121 messagesHash: contentHash
122 )
123
124 emit AgentLoopStarted(
125 agentId: self.agentId,
126 sessionId: sessionId,
127 owner: self.owner!.address
128 )
129
130 return requestId
131 }
132
133 // --- Complete an inference (called by relay transaction) ---
134 access(Operate) fun completeInference(
135 sessionManager: auth(AgentSession.Manage) &AgentSession.SessionManager,
136 agent: auth(AgentRegistry.Execute) &AgentRegistry.Agent,
137 oracleConfig: auth(InferenceOracle.Relay) &InferenceOracle.OracleConfig,
138 sessionId: UInt64,
139 requestId: UInt64,
140 responseContent: String,
141 responseHash: String,
142 tokensUsed: UInt64,
143 relayAddress: Address
144 ) {
145 // 1. Verify relay is authorized for this account
146 assert(
147 oracleConfig.isRelayAuthorized(relayAddress: relayAddress),
148 message: "Relay not authorized for this account"
149 )
150
151 // 2. Verify request hasn't already been completed (dedup)
152 assert(
153 !oracleConfig.isRequestCompleted(requestId: requestId),
154 message: "Request already completed"
155 )
156
157 // 3. Get session and complete inference
158 let session = sessionManager.borrowSession(sessionId: sessionId)
159 ?? panic("Session not found")
160
161 session.completeInference(
162 requestId: requestId,
163 responseContent: responseContent,
164 responseHash: responseHash,
165 tokensUsed: tokensUsed
166 )
167
168 // 4. Mark as completed in oracle (dedup)
169 oracleConfig.markRequestCompleted(requestId: requestId)
170
171 // 5. Record cost (rough estimate: $0.001 per 1000 tokens)
172 let costEstimate = UFix64(tokensUsed) * 0.000001
173 agent.recordCost(amount: costEstimate)
174
175 FlowClaw.totalMessages = FlowClaw.totalMessages + 1
176
177 emit AgentResponseReceived(sessionId: sessionId, contentHash: responseHash)
178 }
179
180 // --- Process tool call result from relay ---
181 access(Operate) fun processToolResult(
182 sessionManager: auth(AgentSession.Manage) &AgentSession.SessionManager,
183 toolCollection: auth(ToolRegistry.ExecuteTools) &ToolRegistry.ToolCollection,
184 sessionId: UInt64,
185 toolCallId: String,
186 toolName: String,
187 output: String,
188 outputHash: String,
189 agentId: UInt64,
190 executionTimeMs: UInt64
191 ) {
192 // 1. Log the execution
193 toolCollection.logExecution(
194 toolName: toolName,
195 agentId: agentId,
196 sessionId: sessionId,
197 inputHash: toolCallId,
198 outputHash: outputHash,
199 status: 1, // completed
200 executionTimeMs: executionTimeMs
201 )
202
203 // 2. Add tool result to session as a message
204 let session = sessionManager.borrowSession(sessionId: sessionId)
205 ?? panic("Session not found")
206
207 session.addMessage(
208 role: "tool",
209 content: output,
210 contentHash: outputHash,
211 toolName: toolName,
212 toolCallId: toolCallId,
213 tokensEstimate: UInt64(output.length / 4)
214 )
215 }
216
217 // --- Store to memory (on-chain) ---
218 access(Operate) fun storeMemory(
219 memoryVault: auth(AgentMemory.Store) &AgentMemory.MemoryVault,
220 key: String,
221 content: String,
222 contentHash: String,
223 tags: [String],
224 source: String
225 ): UInt64 {
226 return memoryVault.store(
227 key: key,
228 content: content,
229 contentHash: contentHash,
230 tags: tags,
231 source: source
232 )
233 }
234
235 // --- Recall from memory ---
236 access(Operate) fun recallMemory(
237 memoryVault: auth(AgentMemory.Recall) &AgentMemory.MemoryVault,
238 key: String
239 ): AgentMemory.MemoryEntry? {
240 return memoryVault.getByKey(key: key)
241 }
242
243 access(Operate) fun recallMemoryByTag(
244 memoryVault: auth(AgentMemory.Recall) &AgentMemory.MemoryVault,
245 tag: String
246 ): [AgentMemory.MemoryEntry] {
247 return memoryVault.getByTag(tag: tag)
248 }
249 }
250
251 // -----------------------------------------------------------------------
252 // AccountStatus — public view of a FlowClaw account
253 // -----------------------------------------------------------------------
254 access(all) struct AccountStatus {
255 access(all) let owner: Address
256 access(all) let agentInfo: AgentRegistry.AgentPublicInfo
257 access(all) let sessionCount: Int
258 access(all) let memoryCount: UInt64
259 access(all) let toolCount: Int
260 access(all) let isRelayConfigured: Bool
261
262 init(
263 owner: Address,
264 agentInfo: AgentRegistry.AgentPublicInfo,
265 sessionCount: Int,
266 memoryCount: UInt64,
267 toolCount: Int,
268 isRelayConfigured: Bool
269 ) {
270 self.owner = owner
271 self.agentInfo = agentInfo
272 self.sessionCount = sessionCount
273 self.memoryCount = memoryCount
274 self.toolCount = toolCount
275 self.isRelayConfigured = isRelayConfigured
276 }
277 }
278
279 // -----------------------------------------------------------------------
280 // Public factory
281 // -----------------------------------------------------------------------
282 access(all) fun createAgentStack(ownerAddress: Address, agentId: UInt64): @AgentStack {
283 self.totalAccounts = self.totalAccounts + 1
284 let stack <- create AgentStack(agentId: agentId)
285 emit AccountInitialized(owner: ownerAddress, agentId: agentId)
286 return <- stack
287 }
288
289 // -----------------------------------------------------------------------
290 // Version info
291 // -----------------------------------------------------------------------
292 access(all) fun getVersion(): String {
293 return self.version
294 }
295
296 // -----------------------------------------------------------------------
297 // Init
298 // -----------------------------------------------------------------------
299 init() {
300 self.totalAccounts = 0
301 self.totalMessages = 0
302 self.version = "0.1.0-alpha"
303 self.FlowClawStoragePath = /storage/FlowClawStack
304 }
305}
306