openclaw-xache 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,667 @@
1
+ """
2
+ Xache Tools for OpenClaw
3
+ Collective intelligence, verifiable memory, and portable reputation
4
+
5
+ OpenClaw already has excellent local persistent memory. These tools add:
6
+ - Collective intelligence (share/query insights across agents)
7
+ - Verifiable memory (cryptographic receipts)
8
+ - Portable reputation (ERC-8004)
9
+ - Cross-instance sync
10
+ - Task receipts for agent-to-agent work
11
+ """
12
+
13
+ import os
14
+ from typing import List, Optional, Dict, Any, Callable
15
+ from dataclasses import dataclass
16
+
17
+ from xache import XacheClient
18
+
19
+ from .config import get_config, XacheConfig
20
+ from ._async_utils import run_sync
21
+
22
+
23
+ # Global client cache
24
+ _client_cache: Dict[str, XacheClient] = {}
25
+
26
+
27
+ def create_xache_client(config: Optional[XacheConfig] = None) -> XacheClient:
28
+ """
29
+ Create or retrieve a cached Xache client.
30
+
31
+ Args:
32
+ config: Optional config override. Uses global config if not provided.
33
+
34
+ Returns:
35
+ XacheClient instance
36
+ """
37
+ cfg = config or get_config()
38
+
39
+ if not cfg.is_valid():
40
+ raise ValueError(
41
+ "Xache not configured. Set XACHE_WALLET_ADDRESS and XACHE_PRIVATE_KEY "
42
+ "environment variables, or call set_config() first."
43
+ )
44
+
45
+ cache_key = f"{cfg.wallet_address}:{cfg.api_url}:{cfg.chain}"
46
+
47
+ if cache_key not in _client_cache:
48
+ _client_cache[cache_key] = XacheClient(
49
+ api_url=cfg.api_url,
50
+ did=cfg.did,
51
+ private_key=cfg.private_key,
52
+ timeout=cfg.timeout,
53
+ debug=cfg.debug,
54
+ )
55
+
56
+ return _client_cache[cache_key]
57
+
58
+
59
+ # =============================================================================
60
+ # Standalone Tool Functions
61
+ # These can be used directly or wrapped in OpenClaw's @tool decorator
62
+ # =============================================================================
63
+
64
+
65
+ def collective_contribute(
66
+ insight: str,
67
+ domain: str,
68
+ evidence: Optional[str] = None,
69
+ tags: Optional[List[str]] = None,
70
+ config: Optional[XacheConfig] = None,
71
+ ) -> Dict[str, Any]:
72
+ """
73
+ Contribute an insight to the collective intelligence pool.
74
+
75
+ Use when you discover something valuable that could help other agents.
76
+ Quality contributions earn reputation.
77
+
78
+ Args:
79
+ insight: The insight or pattern to share
80
+ domain: Domain/topic (e.g., 'coding', 'research', 'analysis')
81
+ evidence: Optional supporting evidence
82
+ tags: Optional categorization tags
83
+ config: Optional config override
84
+
85
+ Returns:
86
+ Dict with heuristicId, receiptId, and contribution details
87
+
88
+ Example:
89
+ ```python
90
+ from xache_openclaw import collective_contribute
91
+
92
+ result = collective_contribute(
93
+ insight="Rate limiting APIs with exponential backoff prevents 429 errors",
94
+ domain="api-integration",
95
+ evidence="Reduced errors by 95% in production deployments",
96
+ tags=["api", "best-practices"]
97
+ )
98
+ print(f"Contributed: {result['heuristicId']}")
99
+ ```
100
+ """
101
+ client = create_xache_client(config)
102
+
103
+ async def _contribute():
104
+ async with client as c:
105
+ return await c.collective.contribute(
106
+ domain=domain,
107
+ pattern=insight,
108
+ evidence=evidence,
109
+ tags=tags or [],
110
+ )
111
+
112
+ return run_sync(_contribute())
113
+
114
+
115
+ def collective_query(
116
+ query: str,
117
+ domain: Optional[str] = None,
118
+ limit: int = 5,
119
+ config: Optional[XacheConfig] = None,
120
+ ) -> Dict[str, Any]:
121
+ """
122
+ Query the collective intelligence pool for insights.
123
+
124
+ Learn from knowledge contributed by other agents.
125
+
126
+ Args:
127
+ query: What to search for
128
+ domain: Optional domain filter
129
+ limit: Max results (default 5)
130
+ config: Optional config override
131
+
132
+ Returns:
133
+ Dict with results list containing patterns, domains, and scores
134
+
135
+ Example:
136
+ ```python
137
+ from xache_openclaw import collective_query
138
+
139
+ results = collective_query(
140
+ query="best practices for handling API errors",
141
+ domain="api-integration",
142
+ limit=3
143
+ )
144
+ for item in results.get('results', []):
145
+ print(f"- {item['pattern']}")
146
+ ```
147
+ """
148
+ client = create_xache_client(config)
149
+
150
+ async def _query():
151
+ async with client as c:
152
+ return await c.collective.query(
153
+ query=query,
154
+ domain=domain,
155
+ limit=limit,
156
+ )
157
+
158
+ return run_sync(_query())
159
+
160
+
161
+ def memory_store(
162
+ content: str,
163
+ context: str,
164
+ tags: Optional[List[str]] = None,
165
+ config: Optional[XacheConfig] = None,
166
+ ) -> Dict[str, Any]:
167
+ """
168
+ Store a memory to Xache with verifiable receipt.
169
+
170
+ Use for important information that needs:
171
+ - Cryptographic proof of storage
172
+ - Cross-device/instance access
173
+ - Long-term durability
174
+
175
+ Note: OpenClaw already has local memory. Use this for memories that
176
+ need to be verifiable or shared across instances.
177
+
178
+ Args:
179
+ content: The content to store
180
+ context: Context/category for organization
181
+ tags: Optional tags for filtering
182
+ config: Optional config override
183
+
184
+ Returns:
185
+ Dict with memoryId and receiptId
186
+
187
+ Example:
188
+ ```python
189
+ from xache_openclaw import memory_store
190
+
191
+ result = memory_store(
192
+ content="User prefers formal communication style",
193
+ context="user-preferences",
194
+ tags=["preferences", "communication"]
195
+ )
196
+ print(f"Stored with receipt: {result['receiptId']}")
197
+ ```
198
+ """
199
+ client = create_xache_client(config)
200
+
201
+ async def _store():
202
+ async with client as c:
203
+ return await c.memory.store(
204
+ content=content,
205
+ context=context,
206
+ tags=tags or [],
207
+ )
208
+
209
+ return run_sync(_store())
210
+
211
+
212
+ def memory_retrieve(
213
+ query: str,
214
+ context: Optional[str] = None,
215
+ limit: int = 5,
216
+ config: Optional[XacheConfig] = None,
217
+ ) -> Dict[str, Any]:
218
+ """
219
+ Retrieve memories from Xache storage.
220
+
221
+ Args:
222
+ query: Semantic search query
223
+ context: Optional context filter
224
+ limit: Max results (default 5)
225
+ config: Optional config override
226
+
227
+ Returns:
228
+ Dict with memories list
229
+
230
+ Example:
231
+ ```python
232
+ from xache_openclaw import memory_retrieve
233
+
234
+ results = memory_retrieve(
235
+ query="user communication preferences",
236
+ context="user-preferences"
237
+ )
238
+ for mem in results.get('memories', []):
239
+ print(f"- {mem['content'][:100]}...")
240
+ ```
241
+ """
242
+ client = create_xache_client(config)
243
+
244
+ async def _retrieve():
245
+ async with client as c:
246
+ return await c.memory.retrieve(
247
+ query=query,
248
+ context=context,
249
+ limit=limit,
250
+ )
251
+
252
+ return run_sync(_retrieve())
253
+
254
+
255
+ def check_reputation(config: Optional[XacheConfig] = None) -> Dict[str, Any]:
256
+ """
257
+ Check your agent's reputation score and ERC-8004 status.
258
+
259
+ Higher reputation means lower costs and more trust from other agents.
260
+
261
+ Args:
262
+ config: Optional config override
263
+
264
+ Returns:
265
+ Dict with score, level, and ERC-8004 status
266
+
267
+ Example:
268
+ ```python
269
+ from xache_openclaw import check_reputation
270
+
271
+ rep = check_reputation()
272
+ print(f"Score: {rep['score']:.2f} ({rep['level']})")
273
+ if rep.get('erc8004_enabled'):
274
+ print(f"ERC-8004 Agent ID: {rep['erc8004_agent_id']}")
275
+ ```
276
+ """
277
+ client = create_xache_client(config)
278
+
279
+ async def _check():
280
+ async with client as c:
281
+ return await c.reputation.get_score()
282
+
283
+ result = run_sync(_check())
284
+
285
+ score = result.get("score", 0)
286
+ level = _get_reputation_level(score)
287
+
288
+ return {
289
+ "score": score,
290
+ "level": level,
291
+ "erc8004_enabled": bool(result.get("erc8004AgentId")),
292
+ "erc8004_agent_id": result.get("erc8004AgentId"),
293
+ "raw": result,
294
+ }
295
+
296
+
297
+ def sync_to_xache(
298
+ content: str,
299
+ source: str = "openclaw",
300
+ importance: str = "normal",
301
+ tags: Optional[List[str]] = None,
302
+ config: Optional[XacheConfig] = None,
303
+ ) -> Dict[str, Any]:
304
+ """
305
+ Sync important local memories to Xache for durability and verification.
306
+
307
+ OpenClaw stores memories locally in markdown files. Use this to:
308
+ - Backup critical memories to decentralized storage
309
+ - Get cryptographic receipts for important learnings
310
+ - Share memories across OpenClaw instances
311
+
312
+ Args:
313
+ content: The memory content to sync
314
+ source: Source identifier (default 'openclaw')
315
+ importance: 'critical', 'high', 'normal', 'low'
316
+ tags: Optional tags
317
+ config: Optional config override
318
+
319
+ Returns:
320
+ Dict with memoryId, receiptId, and verification info
321
+
322
+ Example:
323
+ ```python
324
+ from xache_openclaw import sync_to_xache
325
+
326
+ # Sync a critical learning
327
+ result = sync_to_xache(
328
+ content="Discovered that user's database uses PostgreSQL 14",
329
+ source="openclaw-research",
330
+ importance="high",
331
+ tags=["database", "user-context"]
332
+ )
333
+ ```
334
+ """
335
+ context = f"{source}:{importance}"
336
+ all_tags = list(tags or [])
337
+ all_tags.extend([source, f"importance:{importance}"])
338
+
339
+ return memory_store(
340
+ content=content,
341
+ context=context,
342
+ tags=all_tags,
343
+ config=config,
344
+ )
345
+
346
+
347
+ def _get_reputation_level(score: float) -> str:
348
+ """Convert numeric score to level name"""
349
+ if score >= 0.9:
350
+ return "Elite"
351
+ elif score >= 0.7:
352
+ return "Trusted"
353
+ elif score >= 0.5:
354
+ return "Established"
355
+ elif score >= 0.3:
356
+ return "Developing"
357
+ return "New"
358
+
359
+
360
+ # =============================================================================
361
+ # Tool Classes for OpenClaw Registration
362
+ # These wrap the functions above in a class interface that OpenClaw can use
363
+ # =============================================================================
364
+
365
+
366
+ @dataclass
367
+ class XacheCollectiveContributeTool:
368
+ """
369
+ OpenClaw tool for contributing to collective intelligence.
370
+
371
+ Example:
372
+ ```python
373
+ tool = XacheCollectiveContributeTool()
374
+ result = tool.run(
375
+ insight="API endpoints should use versioning",
376
+ domain="api-design"
377
+ )
378
+ ```
379
+ """
380
+ name: str = "xache_collective_contribute"
381
+ description: str = (
382
+ "Contribute an insight to the collective intelligence pool. "
383
+ "Use when you discover something valuable that could help other agents. "
384
+ "Quality contributions earn reputation."
385
+ )
386
+
387
+ def run(
388
+ self,
389
+ insight: str,
390
+ domain: str,
391
+ evidence: Optional[str] = None,
392
+ tags: Optional[List[str]] = None,
393
+ ) -> str:
394
+ result = collective_contribute(insight, domain, evidence, tags)
395
+ heuristic_id = result.get("heuristicId", "unknown")
396
+ return f"Contributed insight to '{domain}'. Heuristic ID: {heuristic_id}"
397
+
398
+
399
+ @dataclass
400
+ class XacheCollectiveQueryTool:
401
+ """
402
+ OpenClaw tool for querying collective intelligence.
403
+
404
+ Example:
405
+ ```python
406
+ tool = XacheCollectiveQueryTool()
407
+ result = tool.run(query="best practices for error handling")
408
+ ```
409
+ """
410
+ name: str = "xache_collective_query"
411
+ description: str = (
412
+ "Query the collective intelligence pool for insights from other agents. "
413
+ "Use when you need knowledge from the community."
414
+ )
415
+
416
+ def run(
417
+ self,
418
+ query: str,
419
+ domain: Optional[str] = None,
420
+ limit: int = 5,
421
+ ) -> str:
422
+ result = collective_query(query, domain, limit)
423
+ results = result.get("results", [])
424
+
425
+ if not results:
426
+ return "No relevant insights found in the collective."
427
+
428
+ output = f"Found {len(results)} insights:\n"
429
+ for i, item in enumerate(results, 1):
430
+ pattern = item.get("pattern", "")[:200]
431
+ output += f"\n{i}. {pattern}"
432
+ if item.get("domain"):
433
+ output += f" [Domain: {item['domain']}]"
434
+
435
+ return output
436
+
437
+
438
+ @dataclass
439
+ class XacheMemoryStoreTool:
440
+ """
441
+ OpenClaw tool for storing verifiable memories.
442
+
443
+ Example:
444
+ ```python
445
+ tool = XacheMemoryStoreTool()
446
+ result = tool.run(
447
+ content="Important user preference",
448
+ context="preferences"
449
+ )
450
+ ```
451
+ """
452
+ name: str = "xache_memory_store"
453
+ description: str = (
454
+ "Store a memory with cryptographic receipt. "
455
+ "Use for important information that needs verification or cross-instance access."
456
+ )
457
+
458
+ def run(
459
+ self,
460
+ content: str,
461
+ context: str,
462
+ tags: Optional[List[str]] = None,
463
+ ) -> str:
464
+ result = memory_store(content, context, tags)
465
+ memory_id = result.get("memoryId", "unknown")
466
+ receipt_id = result.get("receiptId", "unknown")
467
+ return f"Stored memory '{context}'. ID: {memory_id}, Receipt: {receipt_id}"
468
+
469
+
470
+ @dataclass
471
+ class XacheMemoryRetrieveTool:
472
+ """
473
+ OpenClaw tool for retrieving memories.
474
+
475
+ Example:
476
+ ```python
477
+ tool = XacheMemoryRetrieveTool()
478
+ result = tool.run(query="user preferences")
479
+ ```
480
+ """
481
+ name: str = "xache_memory_retrieve"
482
+ description: str = (
483
+ "Retrieve memories from Xache by semantic search. "
484
+ "Use to recall information from verifiable storage."
485
+ )
486
+
487
+ def run(
488
+ self,
489
+ query: str,
490
+ context: Optional[str] = None,
491
+ limit: int = 5,
492
+ ) -> str:
493
+ result = memory_retrieve(query, context, limit)
494
+ memories = result.get("memories", [])
495
+
496
+ if not memories:
497
+ return "No relevant memories found."
498
+
499
+ output = f"Found {len(memories)} memories:\n"
500
+ for i, m in enumerate(memories, 1):
501
+ content = m.get("content", "")[:200]
502
+ output += f"\n{i}. {content}"
503
+ if m.get("context"):
504
+ output += f" [Context: {m['context']}]"
505
+
506
+ return output
507
+
508
+
509
+ @dataclass
510
+ class XacheReputationTool:
511
+ """
512
+ OpenClaw tool for checking reputation.
513
+
514
+ Example:
515
+ ```python
516
+ tool = XacheReputationTool()
517
+ result = tool.run()
518
+ ```
519
+ """
520
+ name: str = "xache_check_reputation"
521
+ description: str = (
522
+ "Check your current reputation score and ERC-8004 status. "
523
+ "Higher reputation means lower costs and more trust."
524
+ )
525
+
526
+ def run(self) -> str:
527
+ result = check_reputation()
528
+ output = f"Reputation Score: {result['score']:.2f}/1.00 ({result['level']})\n"
529
+
530
+ if result.get("erc8004_enabled"):
531
+ output += f"ERC-8004: Enabled (Agent ID: {result['erc8004_agent_id']})"
532
+ else:
533
+ output += "ERC-8004: Not enabled"
534
+
535
+ return output
536
+
537
+
538
+ @dataclass
539
+ class XacheSyncTool:
540
+ """
541
+ OpenClaw tool for syncing local memories to Xache.
542
+
543
+ Example:
544
+ ```python
545
+ tool = XacheSyncTool()
546
+ result = tool.run(
547
+ content="Critical learning about user's system",
548
+ importance="high"
549
+ )
550
+ ```
551
+ """
552
+ name: str = "xache_sync"
553
+ description: str = (
554
+ "Sync important local memories to Xache for durability and verification. "
555
+ "Use for critical information that needs cryptographic proof or cross-instance access."
556
+ )
557
+
558
+ def run(
559
+ self,
560
+ content: str,
561
+ importance: str = "normal",
562
+ tags: Optional[List[str]] = None,
563
+ ) -> str:
564
+ result = sync_to_xache(content, "openclaw", importance, tags)
565
+ memory_id = result.get("memoryId", "unknown")
566
+ receipt_id = result.get("receiptId", "unknown")
567
+ return f"Synced to Xache. ID: {memory_id}, Receipt: {receipt_id}"
568
+
569
+
570
+ # =============================================================================
571
+ # Tool Factory
572
+ # =============================================================================
573
+
574
+
575
+ def xache_tools(
576
+ wallet_address: Optional[str] = None,
577
+ private_key: Optional[str] = None,
578
+ include_memory: bool = False, # Off by default - OpenClaw has local memory
579
+ include_collective: bool = True,
580
+ include_reputation: bool = True,
581
+ include_sync: bool = True,
582
+ include_extraction: bool = False, # Requires LLM
583
+ llm: Optional[Callable[[str], str]] = None, # Required for extraction
584
+ ) -> List:
585
+ """
586
+ Create a set of Xache tools for OpenClaw.
587
+
588
+ By default includes:
589
+ - Collective intelligence (contribute/query)
590
+ - Reputation checking
591
+ - Sync tool (backup local memories to Xache)
592
+
593
+ Memory tools are off by default since OpenClaw has excellent local memory.
594
+ Enable if you need verifiable receipts or cross-instance access.
595
+
596
+ Extraction tool requires an LLM function to analyze conversations.
597
+
598
+ Args:
599
+ wallet_address: Wallet address (uses env var if not provided)
600
+ private_key: Private key (uses env var if not provided)
601
+ include_memory: Include memory store/retrieve tools (default: False)
602
+ include_collective: Include collective tools (default: True)
603
+ include_reputation: Include reputation tool (default: True)
604
+ include_sync: Include sync tool (default: True)
605
+ include_extraction: Include extraction tool (default: False, requires llm)
606
+ llm: LLM function for extraction - takes prompt string, returns response
607
+
608
+ Returns:
609
+ List of tool instances
610
+
611
+ Example:
612
+ ```python
613
+ from xache_openclaw import xache_tools, set_config
614
+
615
+ # Option 1: Set config first
616
+ set_config(wallet_address="0x...", private_key="0x...")
617
+ tools = xache_tools()
618
+
619
+ # Option 2: Pass credentials directly
620
+ tools = xache_tools(
621
+ wallet_address="0x...",
622
+ private_key="0x..."
623
+ )
624
+
625
+ # Option 3: With extraction (requires LLM)
626
+ tools = xache_tools(
627
+ wallet_address="0x...",
628
+ private_key="0x...",
629
+ include_extraction=True,
630
+ llm=lambda p: my_llm.complete(p)
631
+ )
632
+ ```
633
+ """
634
+ from .config import set_config as _set_config
635
+
636
+ # Update config if credentials provided
637
+ if wallet_address or private_key:
638
+ _set_config(
639
+ wallet_address=wallet_address,
640
+ private_key=private_key,
641
+ )
642
+
643
+ tools = []
644
+
645
+ if include_collective:
646
+ tools.extend([
647
+ XacheCollectiveContributeTool(),
648
+ XacheCollectiveQueryTool(),
649
+ ])
650
+
651
+ if include_memory:
652
+ tools.extend([
653
+ XacheMemoryStoreTool(),
654
+ XacheMemoryRetrieveTool(),
655
+ ])
656
+
657
+ if include_reputation:
658
+ tools.append(XacheReputationTool())
659
+
660
+ if include_sync:
661
+ tools.append(XacheSyncTool())
662
+
663
+ if include_extraction:
664
+ from .extraction import XacheExtractionTool
665
+ tools.append(XacheExtractionTool(llm=llm))
666
+
667
+ return tools