Rohan03 commited on
Commit
eef94ae
Β·
verified Β·
1 Parent(s): 5c598da

V2 merge: purpose_agent/memory_ci.py

Browse files
Files changed (1) hide show
  1. purpose_agent/memory_ci.py +137 -0
purpose_agent/memory_ci.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ memory_ci.py β€” Memory Continuous Integration pipeline.
3
+
4
+ Candidate memories go through a strict promotion pipeline:
5
+
6
+ candidate β†’ immune_scan β†’ quarantine β†’ replay_test β†’ promote/reject
7
+
8
+ No memory reaches the agent's prompt without passing every gate.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from typing import Any
14
+
15
+ from purpose_agent.memory import MemoryCard, MemoryKind, MemoryStatus, MemoryStore
16
+ from purpose_agent.immune import scan_memory, ScanResult
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class MemoryCI:
22
+ """
23
+ Continuous integration pipeline for agent memories.
24
+
25
+ Usage:
26
+ ci = MemoryCI(memory_store)
27
+
28
+ # Submit a candidate memory
29
+ card = MemoryCard(kind=MemoryKind.SKILL_CARD, pattern="...", strategy="...")
30
+ result = ci.submit(card)
31
+
32
+ # result.status is now QUARANTINED (passed scan) or REJECTED (failed scan)
33
+
34
+ # After replay testing confirms it helps:
35
+ ci.promote(card.id)
36
+
37
+ # Or if replay shows it hurts:
38
+ ci.reject(card.id, reason="decreased success rate in replay test")
39
+ """
40
+
41
+ def __init__(self, store: MemoryStore):
42
+ self.store = store
43
+ self._scan_log: list[dict[str, Any]] = []
44
+
45
+ def submit(self, card: MemoryCard) -> MemoryCard:
46
+ """
47
+ Submit a candidate memory for the CI pipeline.
48
+
49
+ Steps:
50
+ 1. Add to store as CANDIDATE
51
+ 2. Run immune scan
52
+ 3. If scan passes β†’ QUARANTINED (awaiting replay test)
53
+ 4. If scan fails β†’ REJECTED (with reason)
54
+ """
55
+ card.status = MemoryStatus.CANDIDATE
56
+ self.store.add(card)
57
+
58
+ # Immune scan
59
+ scan_result = scan_memory(card)
60
+ card.immune_scan_result = {
61
+ "passed": scan_result.passed,
62
+ "threats": scan_result.threats,
63
+ "severity": scan_result.severity,
64
+ }
65
+
66
+ self._scan_log.append({
67
+ "card_id": card.id,
68
+ "kind": card.kind.value,
69
+ "scan_passed": scan_result.passed,
70
+ "threats": scan_result.threats,
71
+ })
72
+
73
+ if scan_result.passed:
74
+ self.store.update_status(card.id, MemoryStatus.QUARANTINED)
75
+ logger.info(f"MemoryCI: {card.id} β†’ QUARANTINED (scan passed)")
76
+ else:
77
+ reason = f"Immune scan failed: {', '.join(scan_result.threats)} (severity={scan_result.severity})"
78
+ self.store.update_status(card.id, MemoryStatus.REJECTED, reason=reason)
79
+ logger.warning(f"MemoryCI: {card.id} β†’ REJECTED ({reason})")
80
+
81
+ return card
82
+
83
+ def promote(self, card_id: str) -> bool:
84
+ """
85
+ Promote a quarantined memory to active use.
86
+ Only quarantined memories can be promoted.
87
+ """
88
+ card = self.store.get(card_id)
89
+ if not card:
90
+ logger.warning(f"MemoryCI: card {card_id} not found")
91
+ return False
92
+
93
+ if card.status != MemoryStatus.QUARANTINED:
94
+ logger.warning(
95
+ f"MemoryCI: cannot promote {card_id} β€” "
96
+ f"status is {card.status.value}, expected quarantined"
97
+ )
98
+ return False
99
+
100
+ self.store.update_status(card_id, MemoryStatus.PROMOTED)
101
+ logger.info(f"MemoryCI: {card_id} β†’ PROMOTED")
102
+ return True
103
+
104
+ def reject(self, card_id: str, reason: str = "") -> bool:
105
+ """Reject a memory (from any non-promoted status)."""
106
+ card = self.store.get(card_id)
107
+ if not card:
108
+ return False
109
+
110
+ if card.status == MemoryStatus.PROMOTED:
111
+ # Demote: promoted β†’ archived (don't delete, keep for audit)
112
+ self.store.update_status(card_id, MemoryStatus.ARCHIVED, reason=reason)
113
+ logger.info(f"MemoryCI: {card_id} DEMOTED β†’ ARCHIVED ({reason})")
114
+ else:
115
+ self.store.update_status(card_id, MemoryStatus.REJECTED, reason=reason)
116
+ logger.info(f"MemoryCI: {card_id} β†’ REJECTED ({reason})")
117
+ return True
118
+
119
+ def archive(self, card_id: str) -> bool:
120
+ """Archive a promoted memory (soft delete β€” keeps for audit trail)."""
121
+ card = self.store.get(card_id)
122
+ if not card:
123
+ return False
124
+ self.store.update_status(card_id, MemoryStatus.ARCHIVED)
125
+ return True
126
+
127
+ def get_quarantined(self) -> list[MemoryCard]:
128
+ """Get all memories awaiting replay testing."""
129
+ return self.store.get_by_status(MemoryStatus.QUARANTINED)
130
+
131
+ def get_rejected(self) -> list[MemoryCard]:
132
+ """Get all rejected memories (for audit)."""
133
+ return self.store.get_by_status(MemoryStatus.REJECTED)
134
+
135
+ @property
136
+ def scan_log(self) -> list[dict]:
137
+ return self._scan_log