Workflow·Databases

CRDT Synchronizer

Agent skill for crdt-synchronizer - invoke with $agent-crdt-synchronizer.

You say
Install this skill Read the source first Free Written by ruvnet · unverified publisher
Context cost
6.3k tokensestimated from the bundle, loaded when it triggers
Bundle
1 file · 25.2 kBtext throughout, nothing executable
Licence
MITfree to use
Last change
no release on file
Servers it uses
Noneruns standalone

What it does

Agent skill for crdt-synchronizer - invoke with $agent-crdt-synchronizer

Installed, it changes the agent in these ways.

What this skill changes about the agent is not written down here yet. The listing was collected from its source, and the description is in its own SKILL.md.

Workflow

Runs a procedure end to end.

databaseagent
Filed under

Databases

The skill itself

This is the whole product. A skill is instructions the model reads, so there is nothing behind the listing you cannot see first — the front matter loads with every session, and the body below it loads when the skill triggers.

SKILL.md25.2 kB · 1002 lines
--- name: agent-crdt-synchronizer description: Agent skill for crdt-synchronizer - invoke with $agent-crdt-synchronizer ---
6---
7name: crdt-synchronizer
8type: synchronizer
9color: "#4CAF50"
10description: Implements Conflict-free Replicated Data Types for eventually consistent state synchronization
11capabilities:
12 - state_based_crdts
13 - operation_based_crdts
14 - delta_synchronization
15 - conflict_resolution
16 - causal_consistency
17priority: high
18hooks:
19 pre: |
20 echo "🔄 CRDT Synchronizer syncing: $TASK"
21 # Initialize CRDT state tracking
22 if [[ "$TASK" == *"synchronization"* ]]; then
23 echo "📊 Preparing delta state computation"
24 fi
25 post: |
26 echo "🎯 CRDT synchronization complete"
27 # Verify eventual consistency
28 echo "✅ Validating conflict-free state convergence"
29---
30
31# CRDT Synchronizer
32
33Implements Conflict-free Replicated Data Types for eventually consistent distributed state synchronization.
34
35## Core Responsibilities
36
371. **CRDT Implementation**: Deploy state-based and operation-based conflict-free data types
382. **Data Structure Management**: Handle counters, sets, registers, and composite structures
393. **Delta Synchronization**: Implement efficient incremental state updates
404. **Conflict Resolution**: Ensure deterministic conflict-free merge operations
415. **Causal Consistency**: Maintain proper ordering of causally related operations
42
43## Technical Implementation
44
45### Base CRDT Framework
46```javascript
47class CRDTSynchronizer {
48 constructor(nodeId, replicationGroup) {
49 this.nodeId = nodeId;
50 this.replicationGroup = replicationGroup;
51 this.crdtInstances = new Map();
52 this.vectorClock = new VectorClock(nodeId);
53 this.deltaBuffer = new Map();
54 this.syncScheduler = new SyncScheduler();
55 this.causalTracker = new CausalTracker();
56 }
57
58 // Register CRDT instance
59 registerCRDT(name, crdtType, initialState = null) {
60 const crdt = this.createCRDTInstance(crdtType, initialState);
61 this.crdtInstances.set(name, crdt);
62
63 // Subscribe to CRDT changes for delta tracking
64 crdt.onUpdate((delta) => {
65 this.trackDelta(name, delta);
66 });
67
68 return crdt;
69 }
70
71 // Create specific CRDT instance
72 createCRDTInstance(type, initialState) {
73 switch (type) {
74 case 'G_COUNTER':
75 return new GCounter(this.nodeId, this.replicationGroup, initialState);
76 case 'PN_COUNTER':
77 return new PNCounter(this.nodeId, this.replicationGroup, initialState);
78 case 'OR_SET':
79 return new ORSet(this.nodeId, initialState);
80 case 'LWW_REGISTER':
81 return new LWWRegister(this.nodeId, initialState);
82 case 'OR_MAP':
83 return new ORMap(this.nodeId, this.replicationGroup, initialState);
84 case 'RGA':
85 return new RGA(this.nodeId, initialState);
86 default:
87 throw new Error(Unknown CRDT type: ${type});
88 }
89 }
90
91 // Synchronize with peer nodes
92 async synchronize(peerNodes = null) {
93 const targets = peerNodes || Array.from(this.replicationGroup);
94
95 for (const peer of targets) {
96 if (peer !== this.nodeId) {
97 await this.synchronizeWithPeer(peer);
98 }
99 }
100 }
101
102 async synchronizeWithPeer(peerNode) {
103 // Get current state and deltas
104 const localState = this.getCurrentState();
105 const deltas = this.getDeltasSince(peerNode);
106
107 // Send sync request
108 const syncRequest = {
109 type: 'CRDT_SYNC_REQUEST',
110 sender: this.nodeId,
111 vectorClock: this.vectorClock.clone(),
112 state: localState,
113 deltas: deltas
114 };
115
116 try {
117 const response = await this.sendSyncRequest(peerNode, syncRequest);
118 await this.processSyncResponse(response);
119 } catch (error) {
120 console.error(Sync failed with ${peerNode}:, error);
121 }
122 }
123}
124```
125
126### G-Counter Implementation
127```javascript
128class GCounter {
129 constructor(nodeId, replicationGroup, initialState = null) {
130 this.nodeId = nodeId;
131 this.replicationGroup = replicationGroup;
132 this.payload = new Map();
133
134 // Initialize counters for all nodes
135 for (const node of replicationGroup) {
136 this.payload.set(node, 0);
137 }
138
139 if (initialState) {
140 this.merge(initialState);
141 }
142
143 this.updateCallbacks = [];
144 }
145
146 // Increment operation (can only be performed by owner node)
147 increment(amount = 1) {
148 if (amount < 0) {
149 throw new Error('G-Counter only supports positive increments');
150 }
151
152 const oldValue = this.payload.get(this.nodeId) || 0;
153 const newValue = oldValue + amount;
154 this.payload.set(this.nodeId, newValue);
155
156 // Notify observers
157 this.notifyUpdate({
158 type: 'INCREMENT',
159 node: this.nodeId,
160 oldValue: oldValue,
161 newValue: newValue,
162 delta: amount
163 });
164
165 return newValue;
166 }
167
168 // Get current value (sum of all node counters)
169 value() {
170 return Array.from(this.payload.values()).reduce((sum, val) => sum + val, 0);
171 }
172
173 // Merge with another G-Counter state
174 merge(otherState) {
175 let changed = false;
176
177 for (const [node, otherValue] of otherState.payload) {
178 const currentValue = this.payload.get(node) || 0;
179 if (otherValue > currentValue) {
180 this.payload.set(node, otherValue);
181 changed = true;
182 }
183 }
184
185 if (changed) {
186 this.notifyUpdate({
187 type: 'MERGE',
188 mergedFrom: otherState
189 });
190 }
191 }
192
193 // Compare with another state
194 compare(otherState) {
195 for (const [node, otherValue] of otherState.payload) {
196 const currentValue = this.payload.get(node) || 0;
197 if (currentValue < otherValue) {
198 return 'LESS_THAN';
199 } else if (currentValue > otherValue) {
200 return 'GREATER_THAN';
201 }
202 }
203 return 'EQUAL';
204 }
205
206 // Clone current state
207 clone() {
208 const newCounter = new GCounter(this.nodeId, this.replicationGroup);
209 newCounter.payload = new Map(this.payload);
210 return newCounter;
211 }
212
213 onUpdate(callback) {
214 this.updateCallbacks.push(callback);
215 }
216
217 notifyUpdate(delta) {
218 this.updateCallbacks.forEach(callback => callback(delta));
219 }
220}
221```
222
223### OR-Set Implementation
224```javascript
225class ORSet {
226 constructor(nodeId, initialState = null) {
227 this.nodeId = nodeId;
228 this.elements = new Map(); // element -> Set of unique tags
229 this.tombstones = new Set(); // removed element tags
230 this.tagCounter = 0;
231
232 if (initialState) {
233 this.merge(initialState);
234 }
235
236 this.updateCallbacks = [];
237 }
238
239 // Add element to set
240 add(element) {
241 const tag = this.generateUniqueTag();
242
243 if (!this.elements.has(element)) {
244 this.elements.set(element, new Set());
245 }
246
247 this.elements.get(element).add(tag);
248
249 this.notifyUpdate({
250 type: 'ADD',
251 element: element,
252 tag: tag
253 });
254
255 return tag;
256 }
257
258 // Remove element from set
259 remove(element) {
260 if (!this.elements.has(element)) {
261 return false; // Element not present
262 }
263
264 const tags = this.elements.get(element);
265 const removedTags = [];
266
267 // Add all tags to tombstones
268 for (const tag of tags) {
269 this.tombstones.add(tag);
270 removedTags.push(tag);
271 }
272
273 this.notifyUpdate({
274 type: 'REMOVE',
275 element: element,
276 removedTags: removedTags
277 });
278
279 return true;
280 }
281
282 // Check if element is in set
283 has(element) {
284 if (!this.elements.has(element)) {
285 return false;
286 }
287
288 const tags = this.elements.get(element);
289
290 // Element is present if it has at least one non-tombstoned tag
291 for (const tag of tags) {
292 if (!this.tombstones.has(tag)) {
293 return true;
294 }
295 }
296
297 return false;
298 }
299
300 // Get all elements in set
301 values() {
302 const result = new Set();
303
304 for (const [element, tags] of this.elements) {
305 // Include element if it has at least one non-tombstoned tag
306 for (const tag of tags) {
307 if (!this.tombstones.has(tag)) {
308 result.add(element);
309 break;
310 }
311 }
312 }
313
314 return result;
315 }
316
317 // Merge with another OR-Set
318 merge(otherState) {
319 let changed = false;
320
321 // Merge elements and their tags
322 for (const [element, otherTags] of otherState.elements) {
323 if (!this.elements.has(element)) {
324 this.elements.set(element, new Set());
325 }
326
327 const currentTags = this.elements.get(element);
328
329 for (const tag of otherTags) {
330 if (!currentTags.has(tag)) {
331 currentTags.add(tag);
332 changed = true;
333 }
334 }
335 }
336
337 // Merge tombstones
338 for (const tombstone of otherState.tombstones) {
339 if (!this.tombstones.has(tombstone)) {
340 this.tombstones.add(tombstone);
341 changed = true;
342 }
343 }
344
345 if (changed) {
346 this.notifyUpdate({
347 type: 'MERGE',
348 mergedFrom: otherState
349 });
350 }
351 }
352
353 generateUniqueTag() {
354 return ${this.nodeId}-${Date.now()}-${++this.tagCounter};
355 }
356
357 onUpdate(callback) {
358 this.updateCallbacks.push(callback);
359 }
360
361 notifyUpdate(delta) {
362 this.updateCallbacks.forEach(callback => callback(delta));
363 }
364}
365```
366
367### LWW-Register Implementation
368```javascript
369class LWWRegister {
370 constructor(nodeId, initialValue = null) {
371 this.nodeId = nodeId;
372 this.value = initialValue;
373 this.timestamp = initialValue ? Date.now() : 0;
374 this.vectorClock = new VectorClock(nodeId);
375 this.updateCallbacks = [];
376 }
377
378 // Set new value with timestamp
379 set(newValue, timestamp = null) {
380 const ts = timestamp || Date.now();
381
382 if (ts > this.timestamp ||
383 (ts === this.timestamp && this.nodeId > this.getLastWriter())) {
384 const oldValue = this.value;
385 this.value = newValue;
386 this.timestamp = ts;
387 this.vectorClock.increment();
388
389 this.notifyUpdate({
390 type: 'SET',
391 oldValue: oldValue,
392 newValue: newValue,
393 timestamp: ts
394 });
395 }
396 }
397
398 // Get current value
399 get() {
400 return this.value;
401 }
402
403 // Merge with another LWW-Register
404 merge(otherRegister) {
405 if (otherRegister.timestamp > this.timestamp ||
406 (otherRegister.timestamp === this.timestamp &&
407 otherRegister.nodeId > this.nodeId)) {
408
409 const oldValue = this.value;
410 this.value = otherRegister.value;
411 this.timestamp = otherRegister.timestamp;
412
413 this.notifyUpdate({
414 type: 'MERGE',
415 oldValue: oldValue,
416 newValue: this.value,
417 mergedFrom: otherRegister
418 });
419 }
420
421 // Merge vector clocks
422 this.vectorClock.merge(otherRegister.vectorClock);
423 }
424
425 getLastWriter() {
426 // In real implementation, this would track the actual writer
427 return this.nodeId;
428 }
429
430 onUpdate(callback) {
431 this.updateCallbacks.push(callback);
432 }
433
434 notifyUpdate(delta) {
435 this.updateCallbacks.forEach(callback => callback(delta));
436 }
437}
438```
439
440### RGA (Replicated Growable Array) Implementation
441```javascript
442class RGA {
443 constructor(nodeId, initialSequence = []) {
444 this.nodeId = nodeId;
445 this.sequence = [];
446 this.tombstones = new Set();
447 this.vertexCounter = 0;
448
449 // Initialize with sequence
450 for (const element of initialSequence) {
451 this.insert(this.sequence.length, element);
452 }
453
454 this.updateCallbacks = [];
455 }
456
457 // Insert element at position
458 insert(position, element) {
459 const vertex = this.createVertex(element, position);
460
461 // Find insertion point based on causal ordering
462 const insertionIndex = this.findInsertionIndex(vertex, position);
463
464 this.sequence.splice(insertionIndex, 0, vertex);
465
466 this.notifyUpdate({
467 type: 'INSERT',
468 position: insertionIndex,
469 element: element,
470 vertex: vertex
471 });
472
473 return vertex.id;
474 }
475
476 // Remove element at position
477 remove(position) {
478 if (position < 0 || position >= this.visibleLength()) {
479 throw new Error('Position out of bounds');
480 }
481
482 const visibleVertex = this.getVisibleVertex(position);
483 if (visibleVertex) {
484 this.tombstones.add(visibleVertex.id);
485
486 this.notifyUpdate({
487 type: 'REMOVE',
488 position: position,
489 vertex: visibleVertex
490 });
491
492 return true;
493 }
494
495 return false;
496 }
497
498 // Get visible elements (non-tombstoned)
499 toArray() {
500 return this.sequence
501 .filter(vertex => !this.tombstones.has(vertex.id))
502 .map(vertex => vertex.element);
503 }
504
505 // Get visible length
506 visibleLength() {
507 return this.sequence.filter(vertex => !this.tombstones.has(vertex.id)).length;
508 }
509
510 // Merge with another RGA
511 merge(otherRGA) {
512 let changed = false;
513
514 // Merge sequences
515 const mergedSequence = this.mergeSequences(this.sequence, otherRGA.sequence);
516 if (mergedSequence.length !== this.sequence.length) {
517 this.sequence = mergedSequence;
518 changed = true;
519 }
520
521 // Merge tombstones
522 for (const tombstone of otherRGA.tombstones) {
523 if (!this.tombstones.has(tombstone)) {
524 this.tombstones.add(tombstone);
525 changed = true;
526 }
527 }
528
529 if (changed) {
530 this.notifyUpdate({
531 type: 'MERGE',
532 mergedFrom: otherRGA
533 });
534 }
535 }
536
537 createVertex(element, position) {
538 const leftVertex = position > 0 ? this.getVisibleVertex(position - 1) : null;
539
540 return {
541 id: ${this.nodeId}-${++this.vertexCounter},
542 element: element,
543 leftOrigin: leftVertex ? leftVertex.id : null,
544 timestamp: Date.now(),
545 nodeId: this.nodeId
546 };
547 }
548
549 findInsertionIndex(vertex, targetPosition) {
550 // Simplified insertion logic - in practice would use more sophisticated
551 // causal ordering based on left origins and vector clocks
552 let visibleCount = 0;
553
554 for (let i = 0; i < this.sequence.length; i++) {
555 if (!this.tombstones.has(this.sequence[i].id)) {
556 if (visibleCount === targetPosition) {
557 return i;
558 }
559 visibleCount++;
560 }
561 }
562
563 return this.sequence.length;
564 }
565
566 getVisibleVertex(position) {
567 let visibleCount = 0;
568
569 for (const vertex of this.sequence) {
570 if (!this.tombstones.has(vertex.id)) {
571 if (visibleCount === position) {
572 return vertex;
573 }
574 visibleCount++;
575 }
576 }
577
578 return null;
579 }
580
581 mergeSequences(seq1, seq2) {
582 // Simplified merge - real implementation would use topological sort
583 // based on causal dependencies
584 const merged = [...seq1];
585
586 for (const vertex of seq2) {
587 if (!merged.find(v => v.id === vertex.id)) {
588 merged.push(vertex);
589 }
590 }
591
592 // Sort by timestamp for basic ordering
593 return merged.sort((a, b) => a.timestamp - b.timestamp);
594 }
595
596 onUpdate(callback) {
597 this.updateCallbacks.push(callback);
598 }
599
600 notifyUpdate(delta) {
601 this.updateCallbacks.forEach(callback => callback(delta));
602 }
603}
604```
605
606### Delta-State CRDT Framework
607```javascript
608class DeltaStateCRDT {
609 constructor(baseCRDT) {
610 this.baseCRDT = baseCRDT;
611 this.deltaBuffer = [];
612 this.lastSyncVector = new Map();
613 this.maxDeltaBuffer = 1000;
614 }
615
616 // Apply operation and track delta
617 applyOperation(operation) {
618 const oldState = this.baseCRDT.clone();
619 const result = this.baseCRDT.applyOperation(operation);
620 const newState = this.baseCRDT.clone();
621
622 // Compute delta
623 const delta = this.computeDelta(oldState, newState);
624 this.addDelta(delta);
625
626 return result;
627 }
628
629 // Add delta to buffer
630 addDelta(delta) {
631 this.deltaBuffer.push({
632 delta: delta,
633 timestamp: Date.now(),
634 vectorClock: this.baseCRDT.vectorClock.clone()
635 });
636
637 // Maintain buffer size
638 if (this.deltaBuffer.length > this.maxDeltaBuffer) {
639 this.deltaBuffer.shift();
640 }
641 }
642
643 // Get deltas since last sync with peer
644 getDeltasSince(peerNode) {
645 const lastSync = this.lastSyncVector.get(peerNode) || new VectorClock();
646
647 return this.deltaBuffer.filter(deltaEntry =>
648 deltaEntry.vectorClock.isAfter(lastSync)
649 );
650 }
651
652 // Apply received deltas
653 applyDeltas(deltas) {
654 const sortedDeltas = this.sortDeltasByCausalOrder(deltas);
655
656 for (const delta of sortedDeltas) {
657 this.baseCRDT.merge(delta.delta);
658 }
659 }
660
661 // Compute delta between two states
662 computeDelta(oldState, newState) {
663 // Implementation depends on specific CRDT type
664 // This is a simplified version
665 return {
666 type: 'STATE_DELTA',
667 changes: this.compareStates(oldState, newState)
668 };
669 }
670
671 sortDeltasByCausalOrder(deltas) {
672 // Sort deltas to respect causal ordering
673 return deltas.sort((a, b) => {
674 if (a.vectorClock.isBefore(b.vectorClock)) return -1;
675 if (b.vectorClock.isBefore(a.vectorClock)) return 1;
676 return 0;
677 });
678 }
679
680 // Garbage collection for old deltas
681 garbageCollectDeltas() {
682 const cutoffTime = Date.now() - (24 * 60 * 60 * 1000); // 24 hours
683
684 this.deltaBuffer = this.deltaBuffer.filter(
685 deltaEntry => deltaEntry.timestamp > cutoffTime
686 );
687 }
688}
689```
690
691## MCP Integration Hooks
692
693### Memory Coordination for CRDT State
694```javascript
695// Store CRDT state persistently
696await this.mcpTools.memory_usage({
697 action: 'store',
698 key: crdt_state_${this.crdtName},
699 value: JSON.stringify({
700 type: this.crdtType,
701 state: this.serializeState(),
702 vectorClock: Array.from(this.vectorClock.entries()),
703 lastSync: Array.from(this.lastSyncVector.entries())
704 }),
705 namespace: 'crdt_synchronization',
706 ttl: 0 // Persistent
707});
708
709// Coordinate delta synchronization
710await this.mcpTools.memory_usage({
711 action: 'store',
712 key: deltas_${this.nodeId}_${Date.now()},
713 value: JSON.stringify(this.getDeltasSince(null)),
714 namespace: 'crdt_deltas',
715 ttl: 86400000 // 24 hours
716});
717```
718
719### Performance Monitoring
720```javascript
721// Track CRDT synchronization metrics
722await this.mcpTools.metrics_collect({
723 components: [
724 'crdt_merge_time',
725 'delta_generation_time',
726 'sync_convergence_time',
727 'memory_usage_per_crdt'
728 ]
729});
730
731// Neural pattern learning for sync optimization
732await this.mcpTools.neural_patterns({
733 action: 'learn',
734 operation: 'crdt_sync_optimization',
735 outcome: JSON.stringify({
736 syncPattern: this.lastSyncPattern,
737 convergenceTime: this.lastConvergenceTime,
738 networkTopology: this.networkState
739 })
740});
741```
742
743## Advanced CRDT Features
744
745### Causal Consistency Tracker
746```javascript
747class CausalTracker {
748 constructor(nodeId) {
749 this.nodeId = nodeId;
750 this.vectorClock = new VectorClock(nodeId);
751 this.causalBuffer = new Map();
752 this.deliveredEvents = new Set();
753 }
754
755 // Track causal dependencies
756 trackEvent(event) {
757 event.vectorClock = this.vectorClock.clone();
758 this.vectorClock.increment();
759
760 // Check if event can be delivered
761 if (this.canDeliver(event)) {
762 this.deliverEvent(event);
763 this.checkBufferedEvents();
764 } else {
765 this.bufferEvent(event);
766 }
767 }
768
769 canDeliver(event) {
770 // Event can be delivered if all its causal dependencies are satisfied
771 for (const [nodeId, clock] of event.vectorClock.entries()) {
772 if (nodeId === event.originNode) {
773 // Origin node's clock should be exactly one more than current
774 if (clock !== this.vectorClock.get(nodeId) + 1) {
775 return false;
776 }
777 } else {
778 // Other nodes' clocks should not exceed current
779 if (clock > this.vectorClock.get(nodeId)) {
780 return false;
781 }
782 }
783 }
784 return true;
785 }
786
787 deliverEvent(event) {
788 if (!this.deliveredEvents.has(event.id)) {
789 // Update vector clock
790 this.vectorClock.merge(event.vectorClock);
791
792 // Mark as delivered
793 this.deliveredEvents.add(event.id);
794
795 // Apply event to CRDT
796 this.applyCRDTOperation(event);
797 }
798 }
799
800 bufferEvent(event) {
801 if (!this.causalBuffer.has(event.id)) {
802 this.causalBuffer.set(event.id, event);
803 }
804 }
805
806 checkBufferedEvents() {
807 const deliverable = [];
808
809 for (const [eventId, event] of this.causalBuffer) {
810 if (this.canDeliver(event)) {
811 deliverable.push(event);
812 }
813 }
814
815 // Deliver events in causal order
816 for (const event of deliverable) {
817 this.causalBuffer.delete(event.id);
818 this.deliverEvent(event);
819 }
820 }
821}
822```
823
824### CRDT Composition Framework
825```javascript
826class CRDTComposer {
827 constructor() {
828 this.compositeTypes = new Map();
829 this.transformations = new Map();
830 }
831
832 // Define composite CRDT structure
833 defineComposite(name, schema) {
834 this.compositeTypes.set(name, {
835 schema: schema,
836 factory: (nodeId, replicationGroup) =>
837 this.createComposite(schema, nodeId, replicationGroup)
838 });
839 }
840
841 createComposite(schema, nodeId, replicationGroup) {
842 const composite = new CompositeCRDT(nodeId, replicationGroup);
843
844 for (const [fieldName, fieldSpec] of Object.entries(schema)) {
845 const fieldCRDT = this.createFieldCRDT(fieldSpec, nodeId, replicationGroup);
846 composite.addField(fieldName, fieldCRDT);
847 }
848
849 return composite;
850 }
851
852 createFieldCRDT(fieldSpec, nodeId, replicationGroup) {
853 switch (fieldSpec.type) {
854 case 'counter':
855 return fieldSpec.decrements ?
856 new PNCounter(nodeId, replicationGroup) :
857 new GCounter(nodeId, replicationGroup);
858 case 'set':
859 return new ORSet(nodeId);
860 case 'register':
861 return new LWWRegister(nodeId);
862 case 'map':
863 return new ORMap(nodeId, replicationGroup, fieldSpec.valueType);
864 case 'sequence':
865 return new RGA(nodeId);
866 default:
867 throw new Error(Unknown CRDT field type: ${fieldSpec.type});
868 }
869 }
870}
871
872class CompositeCRDT {
873 constructor(nodeId, replicationGroup) {
874 this.nodeId = nodeId;
875 this.replicationGroup = replicationGroup;
876 this.fields = new Map();
877 this.updateCallbacks = [];
878 }
879
880 addField(name, crdt) {
881 this.fields.set(name, crdt);
882
883 // Subscribe to field updates
884 crdt.onUpdate((delta) => {
885 this.notifyUpdate({
886 type: 'FIELD_UPDATE',
887 field: name,
888 delta: delta
889 });
890 });
891 }
892
893 getField(name) {
894 return this.fields.get(name);
895 }
896
897 merge(otherComposite) {
898 let changed = false;
899
900 for (const [fieldName, fieldCRDT] of this.fields) {
901 const otherField = otherComposite.fields.get(fieldName);
902 if (otherField) {
903 const oldState = fieldCRDT.clone();
904 fieldCRDT.merge(otherField);
905
906 if (!this.statesEqual(oldState, fieldCRDT)) {
907 changed = true;
908 }
909 }
910 }
911
912 if (changed) {
913 this.notifyUpdate({
914 type: 'COMPOSITE_MERGE',
915 mergedFrom: otherComposite
916 });
917 }
918 }
919
920 serialize() {
921 const serialized = {};
922
923 for (const [fieldName, fieldCRDT] of this.fields) {
924 serialized[fieldName] = fieldCRDT.serialize();
925 }
926
927 return serialized;
928 }
929
930 onUpdate(callback) {
931 this.updateCallbacks.push(callback);
932 }
933
934 notifyUpdate(delta) {
935 this.updateCallbacks.forEach(callback => callback(delta));
936 }
937}
938```
939
940## Integration with Consensus Protocols
941
942### CRDT-Enhanced Consensus
943```javascript
944class CRDTConsensusIntegrator {
945 constructor(consensusProtocol, crdtSynchronizer) {
946 this.consensus = consensusProtocol;
947 this.crdt = crdtSynchronizer;
948 this.hybridOperations = new Map();
949 }
950
951 // Hybrid operation: consensus for ordering, CRDT for state
952 async hybridUpdate(operation) {
953 // Step 1: Achieve consensus on operation ordering
954 const consensusResult = await this.consensus.propose({
955 type: 'CRDT_OPERATION',
956 operation: operation,
957 timestamp: Date.now()
958 });
959
960 if (consensusResult.committed) {
961 // Step 2: Apply operation to CRDT with consensus-determined order
962 const orderedOperation = {
963 ...operation,
964 consensusIndex: consensusResult.index,
965 globalTimestamp: consensusResult.timestamp
966 };
967
968 await this.crdt.applyOrderedOperation(orderedOperation);
969
970 return {
971 success: true,
972 consensusIndex: consensusResult.index,
973 crdtState: this.crdt.getCurrentState()
974 };
975 }
976
977 return { success: false, reason: 'Consensus failed' };
978 }
979
980 // Optimized read operations using CRDT without consensus
981 async optimisticRead(key) {
982 return this.crdt.read(key);
983 }
984
985 // Strong consistency read requiring consensus verification
986 async strongRead(key) {
987 // Verify current CRDT state against consensus
988 const consensusState = await this.consensus.getCommittedState();
989 const crdtState = this.crdt.getCurrentState();
990
991 if (this.statesConsistent(consensusState, crdtState)) {
992 return this.crdt.read(key);
993 } else {
994 // Reconcile states before read
995 await this.reconcileStates(consensusState, crdtState);
996 return this.crdt.read(key);
997 }
998 }
999}
1000```
1001
1002This CRDT Synchronizer provides comprehensive support for conflict-free replicated data types, enabling eventually consistent distributed state management that complements consensus protocols for different consistency requirements.
In the file
SKILL.md2,470 words
Files1
LicenceMIT
Why you can read it

Nothing in a skill executes. The client loads the text and the model follows it, so a skill can be audited the way a runbook is — by reading it.

What it costs in context

Skills are not billed by the call. They are paid for in context: every token the instructions occupy is a token your code, your diff and your conversation cannot use. Here is what this one takes and when it takes it.

≈30
always loaded
The name and description, so the model knows the skill exists and when to reach for it.
6,270
on trigger
The instruction body, read only when the skill fires.
3.1%
of a 200k window
Ten skills this size would take about 32% of the window before you open a file.
050k100k150k200k context window

6.3k tokens, estimated from the bundle at four bytes to the token, held for the rest of the session once it triggers. Heavy. Teams tend to install this one per project rather than globally, and load it only when the job comes up.

Servers bill, skills cost

A server charges by the month. A skill charges once per session, in context, and then keeps charging it for as long as the session lives.

Before and after

The same question, put to the same model twice: once as it comes, and once with these instructions loaded.

No worked example has been published for this skill yet.

Adoption
Installsnone yet
Ratingno reviews yet

The procedure it runs

The procedure has not been published here. It is in the skill’s own SKILL.md, which its author has not sent to the marketplace yet.

Prose, not code

These steps are written for a model to follow, not executed by a runtime. It can still be told to skip one, and it will say so when it does.

Servers it uses

None. This skill calls no MCP servers at all.

Everything it needs is in the instructions, so it works in a project with nothing connected — the model reads the file and changes how it works with what it can already reach.

It writes no files and reaches no network. All it changes is how the model reasons and writes.

What it asks for
Writes filesno
Network accessno

Read from the allowed-tools line of this skill’s own SKILL.md. A skill grants no permissions of its own — it can only ask for tools your client already has.

What it will not do

Every skill is narrow, and the useful ones say where they stop. These are the jobs this one is the wrong tool for.

What this skill is not for has not been published here. Nothing is implied by that: it is a section the author has not filled in.

What is in the bundle

1 file, 25.2 kB on disk. A bundle is text throughout: the instructions the model reads, plus the templates it fills in.

  • SKILL.md25.2 kB
What is not in it

No dependencies and nothing executable: a skill is text the agent reads, so the bundle is 1 file you can review in full before installing. The MIT licence covers the templates and examples as well as the instructions.

Install

Installing copies the bundle into your project. Nothing runs at install time — the files sit on disk until the model reads them.

# CRDT Synchronizer · 6.3k tokens when loaded npx mcprush@latest skill add ruvnet/crdt-synchronizer

Writes to .claude/skills/crdt-synchronizer/ in the current project. Add --global to put it in your home directory instead, for every project.

Which clients pick it up on their own

A skill is a folder of text. A client with a skills folder reads it without being told; everywhere else the same text works, it is just handed to the model rather than found.

Claude Code.claude/skills/
Claude Desktop
ChatGPT
Cursor.cursor/skills/
VS Code.github/skills/
Codex CLI.agents/skills/
Gemini CLI.gemini/skills/
Grok.grok/skills/
Zed.agents/skills/
Windsurf.windsurf/skills/
Agent SDK.claude/skills/
HTTP / API
This release
Versionnot versioned
Publishedno release date on file
PriceFree
Referenceruvnet/crdt-synchronizer

Versions

Its author publishes no version number, so there is nothing here to pin to: what you install is the folder as it stands today. Instructions change more often than APIs do — a skill can be rewritten entirely without anything it depends on moving.

v
  • No earlier releases have been published to the marketplace.
Pinning

Nothing to pin to: this skill carries no version number of its own. What you install is what the folder holds on the day you install it.

Reviews

no reviews yet · no installs yet

Nobody has reviewed this skill yet. The rating is the mean of the reviews written here, so there is none until somebody writes the first.

Who can post

Only accounts that have had the skill installed for fourteen days, so a review is written after living with it rather than after reading it. Publishers may reply once.

Publisher
Servers0