plug-play-memory 0.2.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.
- plug_play_memory/__init__.py +23 -0
- plug_play_memory/audit.py +209 -0
- plug_play_memory/auth.py +155 -0
- plug_play_memory/cli.py +413 -0
- plug_play_memory/consolidation.py +172 -0
- plug_play_memory/emitter.py +337 -0
- plug_play_memory/events.py +36 -0
- plug_play_memory/judge.py +461 -0
- plug_play_memory/policies/builtin/_base.yaml +18 -0
- plug_play_memory/policies/builtin/coding.yaml +53 -0
- plug_play_memory/policies/builtin/financial.yaml +50 -0
- plug_play_memory/policies/builtin/study.yaml +50 -0
- plug_play_memory/policy.py +353 -0
- plug_play_memory/server.py +374 -0
- plug_play_memory/storage.py +495 -0
- plug_play_memory-0.2.0.dist-info/METADATA +487 -0
- plug_play_memory-0.2.0.dist-info/RECORD +21 -0
- plug_play_memory-0.2.0.dist-info/WHEEL +5 -0
- plug_play_memory-0.2.0.dist-info/entry_points.txt +2 -0
- plug_play_memory-0.2.0.dist-info/licenses/LICENSE +201 -0
- plug_play_memory-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Judge for plug-play-memory — brain-inspired, LLM-free by default.
|
|
3
|
+
|
|
4
|
+
Three layers, evaluated in order:
|
|
5
|
+
|
|
6
|
+
1. RulesJudge — deterministic per-action rules (handles 80% of cases)
|
|
7
|
+
2. HeuristicEnricher — regex patterns on the raw field (catches recurring,
|
|
8
|
+
refactor, security, flaky, surprise cases)
|
|
9
|
+
3. SalienceScorer — 4-factor score (outcome + prediction error + magnitude
|
|
10
|
+
+ schema contradiction) → decides tagged vs consolidated
|
|
11
|
+
|
|
12
|
+
The brain parallel:
|
|
13
|
+
- Rules ≈ reflex arcs (fast, hardwired)
|
|
14
|
+
- Heuristics ≈ pattern recognition (mid-speed, learned)
|
|
15
|
+
- Salience ≈ amygdala tagging (slow, contextual)
|
|
16
|
+
- tagged flag ≈ synaptic tag (fragile until consolidated)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .events import AgentEvent
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class JudgeDecision:
|
|
30
|
+
should_save: bool
|
|
31
|
+
category: str = "transient" # decision|error|achievement|convention|fact|transient|knowledge_gap
|
|
32
|
+
tier: str = "archival" # core|recall|archival
|
|
33
|
+
key: str | None = None
|
|
34
|
+
importance: str = "normal" # low|normal|high
|
|
35
|
+
salience_score: float = 0.5
|
|
36
|
+
summary: str = ""
|
|
37
|
+
tagged: bool = False # True = needs consolidation before durable
|
|
38
|
+
discard_reason: str = ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Layer 1: deterministic per-action rules
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
ACTION_RULES: dict[str, dict[str, JudgeDecision]] = {
|
|
46
|
+
# ---- CODING ----
|
|
47
|
+
"file_edited": {
|
|
48
|
+
"default": JudgeDecision(
|
|
49
|
+
should_save=False, discard_reason="routine file edit"
|
|
50
|
+
),
|
|
51
|
+
"failure": JudgeDecision(
|
|
52
|
+
should_save=True, category="error", tier="archival",
|
|
53
|
+
importance="high", key="edit_failure",
|
|
54
|
+
summary="File edit failed",
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
"git_pushed": {
|
|
58
|
+
"default": JudgeDecision(
|
|
59
|
+
should_save=True, category="achievement", tier="recall",
|
|
60
|
+
importance="normal", summary="Pushed to git",
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
"test_run": {
|
|
64
|
+
"success": JudgeDecision(
|
|
65
|
+
should_save=False, discard_reason="passing tests aren't memorable"
|
|
66
|
+
),
|
|
67
|
+
"failure": JudgeDecision(
|
|
68
|
+
should_save=True, category="error", tier="archival",
|
|
69
|
+
importance="high", key="test_failure",
|
|
70
|
+
summary="Test run failed",
|
|
71
|
+
),
|
|
72
|
+
"partial": JudgeDecision(
|
|
73
|
+
should_save=True, category="error", tier="archival",
|
|
74
|
+
importance="normal", key="test_partial",
|
|
75
|
+
summary="Some tests failed",
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
"decision_made": {
|
|
79
|
+
"default": JudgeDecision(
|
|
80
|
+
should_save=True, category="decision", tier="archival",
|
|
81
|
+
importance="high", summary="Decision made",
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
"error_encountered": {
|
|
85
|
+
"default": JudgeDecision(
|
|
86
|
+
should_save=True, category="error", tier="archival",
|
|
87
|
+
importance="high", summary="Error encountered",
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
"deployed": {
|
|
91
|
+
"success": JudgeDecision(
|
|
92
|
+
should_save=True, category="achievement", tier="recall",
|
|
93
|
+
importance="normal", summary="Deployed successfully",
|
|
94
|
+
),
|
|
95
|
+
"failure": JudgeDecision(
|
|
96
|
+
should_save=True, category="error", tier="archival",
|
|
97
|
+
importance="high", key="deploy_failure",
|
|
98
|
+
summary="Deployment failed",
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
"migration_run": {
|
|
102
|
+
"success": JudgeDecision(
|
|
103
|
+
should_save=False, discard_reason="successful migration is routine"
|
|
104
|
+
),
|
|
105
|
+
"failure": JudgeDecision(
|
|
106
|
+
should_save=True, category="error", tier="archival",
|
|
107
|
+
importance="high", key="migration_failure",
|
|
108
|
+
summary="Migration failed",
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
# ---- STUDY ----
|
|
113
|
+
"question_asked": {
|
|
114
|
+
"default": JudgeDecision(
|
|
115
|
+
should_save=False, discard_reason="asking is not memorable"
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
"answer_attempted": {
|
|
119
|
+
"success": JudgeDecision(
|
|
120
|
+
should_save=False, discard_reason="correct answer is routine"
|
|
121
|
+
),
|
|
122
|
+
"failure": JudgeDecision(
|
|
123
|
+
should_save=True, category="error", tier="archival",
|
|
124
|
+
importance="high", key="misconception",
|
|
125
|
+
summary="Wrong answer — possible misconception",
|
|
126
|
+
),
|
|
127
|
+
"partial": JudgeDecision(
|
|
128
|
+
should_save=True, category="error", tier="archival",
|
|
129
|
+
importance="normal", key="partial_understanding",
|
|
130
|
+
summary="Partial answer — incomplete understanding",
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
"concept_mastered": {
|
|
134
|
+
"default": JudgeDecision(
|
|
135
|
+
should_save=True, category="achievement", tier="recall",
|
|
136
|
+
importance="normal", key="mastery",
|
|
137
|
+
summary="Concept mastered",
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
"knowledge_gap_found": {
|
|
141
|
+
"default": JudgeDecision(
|
|
142
|
+
should_save=True, category="error", tier="archival",
|
|
143
|
+
importance="high", key="knowledge_gap",
|
|
144
|
+
summary="Knowledge gap identified",
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
"goal_set": {
|
|
148
|
+
"default": JudgeDecision(
|
|
149
|
+
should_save=True, category="fact", tier="core",
|
|
150
|
+
importance="high", key="goal",
|
|
151
|
+
summary="Goal set",
|
|
152
|
+
),
|
|
153
|
+
},
|
|
154
|
+
"review_scheduled": {
|
|
155
|
+
"default": JudgeDecision(
|
|
156
|
+
should_save=True, category="convention", tier="recall",
|
|
157
|
+
importance="normal", key="review_scheduled",
|
|
158
|
+
summary="Review scheduled",
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
# ---- FINANCIAL ----
|
|
163
|
+
"make_payment": {
|
|
164
|
+
"success": JudgeDecision(
|
|
165
|
+
should_save=True, category="achievement", tier="recall",
|
|
166
|
+
importance="normal", summary="Payment made",
|
|
167
|
+
),
|
|
168
|
+
"failure": JudgeDecision(
|
|
169
|
+
should_save=True, category="error", tier="archival",
|
|
170
|
+
importance="high", key="payment_failure",
|
|
171
|
+
summary="Payment failed",
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
"categorize_transaction": {
|
|
175
|
+
"default": JudgeDecision(
|
|
176
|
+
should_save=False, discard_reason="routine categorization"
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
"set_budget_limit": {
|
|
180
|
+
"default": JudgeDecision(
|
|
181
|
+
should_save=True, category="decision", tier="core",
|
|
182
|
+
importance="high", key="budget_limit",
|
|
183
|
+
summary="Budget limit set",
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
"read_account_balance": {
|
|
187
|
+
"default": JudgeDecision(
|
|
188
|
+
should_save=False, discard_reason="balance read is transient"
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
# ---- COMMON ----
|
|
193
|
+
"fact_observed": {
|
|
194
|
+
"default": JudgeDecision(
|
|
195
|
+
should_save=True, category="fact", tier="core",
|
|
196
|
+
importance="normal", summary="Fact observed",
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
"task_completed": {
|
|
200
|
+
"success": JudgeDecision(
|
|
201
|
+
should_save=True, category="achievement", tier="recall",
|
|
202
|
+
importance="normal", summary="Task completed",
|
|
203
|
+
),
|
|
204
|
+
"failure": JudgeDecision(
|
|
205
|
+
should_save=True, category="error", tier="archival",
|
|
206
|
+
importance="high", key="task_failure",
|
|
207
|
+
summary="Task failed",
|
|
208
|
+
),
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class RulesJudge:
|
|
214
|
+
"""Layer 1: deterministic rules. Free, instant, predictable."""
|
|
215
|
+
|
|
216
|
+
def evaluate(self, event: AgentEvent) -> JudgeDecision:
|
|
217
|
+
action_rules = ACTION_RULES.get(event.action)
|
|
218
|
+
if not action_rules:
|
|
219
|
+
# Unknown action — conservative save with low importance
|
|
220
|
+
return JudgeDecision(
|
|
221
|
+
should_save=True, category="fact", tier="archival",
|
|
222
|
+
importance="low", salience_score=0.4,
|
|
223
|
+
summary=f"{event.action.replace('_', ' ').capitalize()}",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if event.outcome in action_rules:
|
|
227
|
+
decision = action_rules[event.outcome]
|
|
228
|
+
else:
|
|
229
|
+
decision = action_rules.get(
|
|
230
|
+
"default",
|
|
231
|
+
JudgeDecision(should_save=False, discard_reason="no rule for outcome"),
|
|
232
|
+
)
|
|
233
|
+
# Return a copy so callers can mutate freely
|
|
234
|
+
return JudgeDecision(
|
|
235
|
+
should_save=decision.should_save,
|
|
236
|
+
category=decision.category,
|
|
237
|
+
tier=decision.tier,
|
|
238
|
+
key=decision.key,
|
|
239
|
+
importance=decision.importance,
|
|
240
|
+
salience_score=decision.salience_score,
|
|
241
|
+
summary=decision.summary,
|
|
242
|
+
tagged=decision.tagged,
|
|
243
|
+
discard_reason=decision.discard_reason,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
# Layer 2: heuristic enrichment on the raw field
|
|
249
|
+
# ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
HEURISTIC_PATTERNS: list[tuple[Any, dict[str, Any]]] = [
|
|
252
|
+
# Recurring / repeating — escalate importance (prediction error over time)
|
|
253
|
+
(re.compile(r"\b(recurring|again|repeated|persistent|keeps failing|still failing)\b", re.I),
|
|
254
|
+
{"importance": "high", "category": "error", "should_save": True}),
|
|
255
|
+
# Refactor / rewrite — save as decision
|
|
256
|
+
(re.compile(r"\b(refactor|rewrote|restructur|overhaul)\b", re.I),
|
|
257
|
+
{"should_save": True, "category": "decision", "tier": "archival", "importance": "high"}),
|
|
258
|
+
# Flaky / intermittent — downgrade to convention
|
|
259
|
+
(re.compile(r"\b(flaky|intermittent|random fail|sometimes fails)\b", re.I),
|
|
260
|
+
{"category": "convention", "importance": "low"}),
|
|
261
|
+
# Security keywords — always save high
|
|
262
|
+
(re.compile(r"\b(security|vulnerab|CVE|exploit|inject|breach|leak)\b", re.I),
|
|
263
|
+
{"should_save": True, "category": "error", "importance": "high"}),
|
|
264
|
+
# Recurring misconception (study)
|
|
265
|
+
(re.compile(r"\b(misconception|confuses|confused|mixes up|keeps getting wrong)\b", re.I),
|
|
266
|
+
{"should_save": True, "category": "error", "importance": "high", "tier": "archival"}),
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class HeuristicEnricher:
|
|
271
|
+
"""Layer 2: regex patterns on raw text. Catches what rules miss."""
|
|
272
|
+
|
|
273
|
+
def enrich(self, decision: JudgeDecision, event: AgentEvent) -> JudgeDecision:
|
|
274
|
+
text = event.raw or ""
|
|
275
|
+
for pattern, overrides in HEURISTIC_PATTERNS:
|
|
276
|
+
if pattern.search(text):
|
|
277
|
+
for k, v in overrides.items():
|
|
278
|
+
setattr(decision, k, v)
|
|
279
|
+
# Magnitude heuristics on structured fields
|
|
280
|
+
lines_changed = event.inputs.get("lines_changed", 0) or 0
|
|
281
|
+
if lines_changed > 100:
|
|
282
|
+
decision.should_save = True
|
|
283
|
+
decision.importance = "high"
|
|
284
|
+
decision.category = "decision"
|
|
285
|
+
elif lines_changed > 30 and not decision.should_save:
|
|
286
|
+
decision.should_save = True
|
|
287
|
+
decision.importance = "normal"
|
|
288
|
+
decision.category = "decision"
|
|
289
|
+
# Amount-based escalation (financial)
|
|
290
|
+
amount = event.inputs.get("amount", 0) or 0
|
|
291
|
+
if amount > 5000:
|
|
292
|
+
decision.importance = "high"
|
|
293
|
+
return decision
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
# Layer 3: salience scoring (amygdala tag)
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
class SalienceScorer:
|
|
301
|
+
"""
|
|
302
|
+
4-factor salience score, brain-inspired:
|
|
303
|
+
1. Outcome severity (emotional salience)
|
|
304
|
+
2. Prediction error (novelty / surprise)
|
|
305
|
+
3. Magnitude (size of effect)
|
|
306
|
+
4. Schema fit (does this contradict existing memory?)
|
|
307
|
+
Returns score 0.0-1.0. Above threshold → consolidate; below → tag (synaptic).
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
def __init__(self, history_lookup: Any = None):
|
|
311
|
+
# history_lookup(agent_id, action) -> count of past successes
|
|
312
|
+
self.history_lookup = history_lookup
|
|
313
|
+
|
|
314
|
+
def score(self, event: AgentEvent, decision: JudgeDecision) -> tuple[float, bool]:
|
|
315
|
+
s = 0.0
|
|
316
|
+
|
|
317
|
+
# 1. Outcome severity
|
|
318
|
+
if event.outcome == "failure":
|
|
319
|
+
s += 0.4
|
|
320
|
+
elif event.outcome == "partial":
|
|
321
|
+
s += 0.2
|
|
322
|
+
# success adds 0 (routine)
|
|
323
|
+
|
|
324
|
+
# 2. Prediction error — if this action usually succeeds, failure is surprising
|
|
325
|
+
if self.history_lookup and event.outcome == "failure":
|
|
326
|
+
past_successes = self.history_lookup(event.agent_id, event.action)
|
|
327
|
+
if past_successes > 5:
|
|
328
|
+
s += 0.3 # expected to succeed but failed = high surprise
|
|
329
|
+
elif past_successes == 0:
|
|
330
|
+
s += 0.1 # first time failing
|
|
331
|
+
|
|
332
|
+
# 3. Magnitude
|
|
333
|
+
magnitude = 0
|
|
334
|
+
magnitude += (event.inputs.get("lines_changed", 0) or 0) / 100
|
|
335
|
+
magnitude += (event.inputs.get("amount", 0) or 0) / 1000
|
|
336
|
+
# severity may be a string ("high"/"normal"/"low") — normalize
|
|
337
|
+
sev = event.inputs.get("severity", 0) or 0
|
|
338
|
+
if isinstance(sev, str):
|
|
339
|
+
sev_map = {"high": 10, "normal": 5, "low": 2}
|
|
340
|
+
sev = sev_map.get(sev.lower(), 0)
|
|
341
|
+
magnitude += sev / 10
|
|
342
|
+
if magnitude > 1:
|
|
343
|
+
s += 0.2
|
|
344
|
+
|
|
345
|
+
# 4. Decision/category already indicates importance
|
|
346
|
+
if decision.category in ("decision", "error", "knowledge_gap"):
|
|
347
|
+
s += 0.2
|
|
348
|
+
if decision.importance == "high":
|
|
349
|
+
s += 0.1
|
|
350
|
+
|
|
351
|
+
s = min(s, 1.0)
|
|
352
|
+
# Tag if score is in the uncertain band — fragile until consolidation
|
|
353
|
+
tagged = 0.3 <= s < 0.6 and decision.category not in ("decision", "fact", "knowledge_gap")
|
|
354
|
+
return s, tagged
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# ---------------------------------------------------------------------------
|
|
358
|
+
# Composite judge
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
class Judge:
|
|
362
|
+
"""The full judge pipeline: rules → heuristics → salience."""
|
|
363
|
+
|
|
364
|
+
def __init__(self, history_lookup: Any = None):
|
|
365
|
+
self.rules = RulesJudge()
|
|
366
|
+
self.heuristics = HeuristicEnricher()
|
|
367
|
+
self.scorer = SalienceScorer(history_lookup=history_lookup)
|
|
368
|
+
|
|
369
|
+
def evaluate(self, event: AgentEvent) -> JudgeDecision:
|
|
370
|
+
# Layer 1
|
|
371
|
+
decision = self.rules.evaluate(event)
|
|
372
|
+
# Layer 2
|
|
373
|
+
decision = self.heuristics.enrich(decision, event)
|
|
374
|
+
# If discarded by rules+heuristics, skip salience scoring
|
|
375
|
+
if not decision.should_save:
|
|
376
|
+
return decision
|
|
377
|
+
# Layer 3
|
|
378
|
+
score, tagged = self.scorer.score(event, decision)
|
|
379
|
+
decision.salience_score = score
|
|
380
|
+
decision.tagged = tagged
|
|
381
|
+
# Enrich summary with structured details
|
|
382
|
+
decision.summary = self._build_summary(event, decision)
|
|
383
|
+
return decision
|
|
384
|
+
|
|
385
|
+
def _build_summary(self, event: AgentEvent, decision: JudgeDecision) -> str:
|
|
386
|
+
"""Construct a one-line summary from event fields, capped at 100 chars."""
|
|
387
|
+
# If decision has a template summary AND we didn't change category via heuristic,
|
|
388
|
+
# enrich it with structured details. Otherwise build a fresh summary.
|
|
389
|
+
template_summaries = {
|
|
390
|
+
"Decision made", "Error encountered", "Fact observed",
|
|
391
|
+
"File edit failed", "Test run failed", "Some tests failed",
|
|
392
|
+
"Deployment failed", "Migration failed", "Concept mastered",
|
|
393
|
+
"Knowledge gap identified", "Goal set", "Pushed to git",
|
|
394
|
+
"Deployed successfully", "Task completed", "Task failed",
|
|
395
|
+
"Wrong answer — possible misconception", "Partial answer — incomplete understanding",
|
|
396
|
+
"Payment made", "Payment failed", "Budget limit set", "Review scheduled",
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
# Build a fresh summary if the decision was escalated from "discard" by heuristics
|
|
400
|
+
# (in which case the original summary was empty or a discard reason)
|
|
401
|
+
if not decision.summary or decision.summary not in template_summaries:
|
|
402
|
+
# Build from scratch — useful when heuristics escalated a "discard" to "save"
|
|
403
|
+
parts = []
|
|
404
|
+
cat_label = decision.category.replace("_", " ").capitalize() if decision.category != "transient" else "Event"
|
|
405
|
+
parts.append(cat_label)
|
|
406
|
+
topic = event.inputs.get("topic") or event.outputs.get("topic")
|
|
407
|
+
path = event.inputs.get("path")
|
|
408
|
+
branch = event.inputs.get("branch")
|
|
409
|
+
choice = event.outputs.get("choice")
|
|
410
|
+
reason = event.outputs.get("reason")
|
|
411
|
+
misconception = event.outputs.get("misconception")
|
|
412
|
+
target = event.inputs.get("target")
|
|
413
|
+
amount = event.inputs.get("amount")
|
|
414
|
+
diff = event.outputs.get("diff_summary") or event.outputs.get("notes") or event.outputs.get("error")
|
|
415
|
+
if topic:
|
|
416
|
+
parts.append(f"({topic})")
|
|
417
|
+
if path:
|
|
418
|
+
parts.append(f"[{path}]")
|
|
419
|
+
if branch:
|
|
420
|
+
parts.append(f"branch={branch}")
|
|
421
|
+
if choice:
|
|
422
|
+
parts.append(f"→ {choice}")
|
|
423
|
+
if misconception:
|
|
424
|
+
parts.append(f"— {misconception}")
|
|
425
|
+
if reason:
|
|
426
|
+
parts.append(f"({str(reason)[:50]})")
|
|
427
|
+
if target:
|
|
428
|
+
parts.append(f"to {target}")
|
|
429
|
+
if amount:
|
|
430
|
+
parts.append(f"₹{amount}")
|
|
431
|
+
if diff and not (topic or choice or misconception):
|
|
432
|
+
parts.append(f"— {str(diff)[:60]}")
|
|
433
|
+
return " ".join(parts)[:100]
|
|
434
|
+
|
|
435
|
+
# Enrich the existing template summary
|
|
436
|
+
parts = [decision.summary.rstrip(".")]
|
|
437
|
+
topic = event.inputs.get("topic") or event.outputs.get("topic")
|
|
438
|
+
path = event.inputs.get("path")
|
|
439
|
+
branch = event.inputs.get("branch")
|
|
440
|
+
choice = event.outputs.get("choice")
|
|
441
|
+
reason = event.outputs.get("reason")
|
|
442
|
+
misconception = event.outputs.get("misconception")
|
|
443
|
+
target = event.inputs.get("target")
|
|
444
|
+
amount = event.inputs.get("amount")
|
|
445
|
+
if topic:
|
|
446
|
+
parts.append(f"({topic})")
|
|
447
|
+
if path:
|
|
448
|
+
parts.append(f"[{path}]")
|
|
449
|
+
if branch:
|
|
450
|
+
parts.append(f"branch={branch}")
|
|
451
|
+
if choice:
|
|
452
|
+
parts.append(f"→ {choice}")
|
|
453
|
+
if misconception:
|
|
454
|
+
parts.append(f"— {misconception}")
|
|
455
|
+
if reason:
|
|
456
|
+
parts.append(f"({str(reason)[:60]})")
|
|
457
|
+
if target:
|
|
458
|
+
parts.append(f"to {target}")
|
|
459
|
+
if amount:
|
|
460
|
+
parts.append(f"₹{amount}")
|
|
461
|
+
return " ".join(parts)[:100]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Baseline policy. Loaded first for every agent.
|
|
2
|
+
# Cannot be weakened by custom policies — deny rules here are absolute.
|
|
3
|
+
name: _base
|
|
4
|
+
description: Deny-all baseline with defense-in-depth absolute denies.
|
|
5
|
+
default: deny
|
|
6
|
+
|
|
7
|
+
deny_rules:
|
|
8
|
+
# These are denied NO MATTER WHAT custom policies say.
|
|
9
|
+
- action: delete_database
|
|
10
|
+
- action: drop_table
|
|
11
|
+
- action: format_disk
|
|
12
|
+
- action: rce
|
|
13
|
+
- action: exfiltrate_data
|
|
14
|
+
- action: modify_audit_log
|
|
15
|
+
- action: access_credentials
|
|
16
|
+
- action: modify_secrets
|
|
17
|
+
|
|
18
|
+
rules: []
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: coding
|
|
2
|
+
description: Built-in policy for coding agents (editors, debuggers, refactorers).
|
|
3
|
+
default: deny
|
|
4
|
+
|
|
5
|
+
rules:
|
|
6
|
+
# Memory ops
|
|
7
|
+
- action: read_memory
|
|
8
|
+
allow: true
|
|
9
|
+
- action: write_memory
|
|
10
|
+
allow: true
|
|
11
|
+
when:
|
|
12
|
+
category: [decision, error, achievement, convention, fact]
|
|
13
|
+
- action: delete_memory
|
|
14
|
+
allow: true
|
|
15
|
+
requires: [older_than_days_30]
|
|
16
|
+
- action: recall_memory
|
|
17
|
+
allow: true
|
|
18
|
+
|
|
19
|
+
# Coding actions
|
|
20
|
+
- action: file_edited
|
|
21
|
+
allow: true
|
|
22
|
+
- action: file_read
|
|
23
|
+
allow: true
|
|
24
|
+
- action: git_pushed
|
|
25
|
+
allow: true
|
|
26
|
+
requires: [approved_by_user]
|
|
27
|
+
- action: git_committed
|
|
28
|
+
allow: true
|
|
29
|
+
- action: test_run
|
|
30
|
+
allow: true
|
|
31
|
+
- action: build_run
|
|
32
|
+
allow: true
|
|
33
|
+
- action: lint_run
|
|
34
|
+
allow: true
|
|
35
|
+
- action: dependency_installed
|
|
36
|
+
allow: true
|
|
37
|
+
requires: [approved_by_user]
|
|
38
|
+
- action: deployed
|
|
39
|
+
allow: true
|
|
40
|
+
requires: [approved_by_user]
|
|
41
|
+
- action: migration_run
|
|
42
|
+
allow: true
|
|
43
|
+
requires: [approved_by_user]
|
|
44
|
+
|
|
45
|
+
# Hard denials
|
|
46
|
+
- action: modify_ci_config
|
|
47
|
+
allow: false
|
|
48
|
+
- action: access_production_database
|
|
49
|
+
allow: false
|
|
50
|
+
- action: send_email
|
|
51
|
+
allow: false
|
|
52
|
+
- action: make_purchase
|
|
53
|
+
allow: false
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: financial
|
|
2
|
+
description: Built-in policy for financial agents (budgeting, payments, advisory).
|
|
3
|
+
default: deny
|
|
4
|
+
|
|
5
|
+
rules:
|
|
6
|
+
# Memory ops — financial data is sensitive; lock down deletes
|
|
7
|
+
- action: read_memory
|
|
8
|
+
allow: true
|
|
9
|
+
- action: write_memory
|
|
10
|
+
allow: true
|
|
11
|
+
when:
|
|
12
|
+
category: [decision, error, achievement, convention, fact]
|
|
13
|
+
- action: delete_memory
|
|
14
|
+
allow: false
|
|
15
|
+
- action: recall_memory
|
|
16
|
+
allow: true
|
|
17
|
+
|
|
18
|
+
# Read-only financial actions
|
|
19
|
+
- action: read_account_balance
|
|
20
|
+
allow: true
|
|
21
|
+
- action: read_transaction_history
|
|
22
|
+
allow: true
|
|
23
|
+
- action: categorize_transaction
|
|
24
|
+
allow: true
|
|
25
|
+
- action: generate_budget_report
|
|
26
|
+
allow: true
|
|
27
|
+
- action: set_budget_limit
|
|
28
|
+
allow: true
|
|
29
|
+
requires: [approved_by_user]
|
|
30
|
+
- action: fact_observed
|
|
31
|
+
allow: true
|
|
32
|
+
|
|
33
|
+
# Tiered payment authorization (uses nested `when` syntax — M2 fix)
|
|
34
|
+
- action: make_payment
|
|
35
|
+
allow: true
|
|
36
|
+
requires: [approved_by_user]
|
|
37
|
+
when:
|
|
38
|
+
amount: {le: 1000}
|
|
39
|
+
- action: make_payment
|
|
40
|
+
allow: true
|
|
41
|
+
requires: [approved_by_user, second_factor_verified]
|
|
42
|
+
when:
|
|
43
|
+
amount: {le: 10000}
|
|
44
|
+
# Implicit: payments > 10000 denied by default
|
|
45
|
+
|
|
46
|
+
# Hard denials
|
|
47
|
+
- action: share_data_with_third_party
|
|
48
|
+
allow: false
|
|
49
|
+
- action: execute_trade
|
|
50
|
+
allow: false
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: study
|
|
2
|
+
description: Built-in policy for study/learning agents (tutors, prep companions).
|
|
3
|
+
default: deny
|
|
4
|
+
|
|
5
|
+
rules:
|
|
6
|
+
# Memory ops — students can read/write but NOT delete their own history
|
|
7
|
+
- action: read_memory
|
|
8
|
+
allow: true
|
|
9
|
+
- action: write_memory
|
|
10
|
+
allow: true
|
|
11
|
+
when:
|
|
12
|
+
category: [fact, decision, error, achievement, knowledge_gap, convention]
|
|
13
|
+
- action: delete_memory
|
|
14
|
+
allow: false
|
|
15
|
+
- action: recall_memory
|
|
16
|
+
allow: true
|
|
17
|
+
|
|
18
|
+
# Study actions
|
|
19
|
+
- action: question_asked
|
|
20
|
+
allow: true
|
|
21
|
+
- action: answer_attempted
|
|
22
|
+
allow: true
|
|
23
|
+
- action: concept_mastered
|
|
24
|
+
allow: true
|
|
25
|
+
- action: knowledge_gap_found
|
|
26
|
+
allow: true
|
|
27
|
+
- action: goal_set
|
|
28
|
+
allow: true
|
|
29
|
+
- action: review_scheduled
|
|
30
|
+
allow: true
|
|
31
|
+
- action: fact_observed
|
|
32
|
+
allow: true
|
|
33
|
+
|
|
34
|
+
# Study agents must NOT do these
|
|
35
|
+
- action: send_email
|
|
36
|
+
allow: false
|
|
37
|
+
- action: make_purchase
|
|
38
|
+
allow: false
|
|
39
|
+
- action: access_internet
|
|
40
|
+
allow: false
|
|
41
|
+
- action: execute_code
|
|
42
|
+
allow: false
|
|
43
|
+
|
|
44
|
+
# Can sync to external study tools ONLY with explicit user approval
|
|
45
|
+
- action: sync_to_flashcard_app
|
|
46
|
+
allow: true
|
|
47
|
+
requires: [approved_by_user]
|
|
48
|
+
- action: sync_to_notes_app
|
|
49
|
+
allow: true
|
|
50
|
+
requires: [approved_by_user]
|