narcolepticchicken commited on
Commit
c5e904d
·
verified ·
1 Parent(s): 8f9dd28

Upload ledger/ledger.py

Browse files
Files changed (1) hide show
  1. ledger/ledger.py +119 -248
ledger/ledger.py CHANGED
@@ -1,13 +1,9 @@
1
  """
2
- Credit Ledger: non-transferable, decaying, capability-scoped credits with provenance.
3
  """
4
-
5
- import json
6
- import math
7
  import time
8
- from dataclasses import asdict, dataclass, field
9
- from pathlib import Path
10
- from typing import Dict, List, Optional, Tuple
11
 
12
 
13
  @dataclass
@@ -15,50 +11,23 @@ class LedgerEntry:
15
  agent_id: str
16
  task_id: str
17
  action_id: str
18
- earned_credit: float = 0.0
19
- spent_credit: float = 0.0
20
- decayed_credit: float = 0.0
21
- remaining_credit: float = 0.0
22
- reason: str = ""
23
- oracle_score: float = 0.0
24
- compute_cost: float = 0.0
25
- timestamp: float = field(default_factory=time.time)
26
- capability_scope: str = "general"
27
  task_scope: str = "global"
28
- provenance_hash: str = ""
29
 
30
 
31
  class CreditLedger:
32
- """
33
- Immutable-style ledger with decay, non-transferability, and anti-gaming rules.
34
- """
35
-
36
- def __init__(
37
- self,
38
- decay_lambda: float = 0.05, # per-eval decay factor
39
- hoarding_threshold: float = 100.0,
40
- hoarding_window: int = 50,
41
- max_history: int = 10000,
42
- ledger_path: Optional[str] = None,
43
- ):
44
- self.decay_lambda = decay_lambda
45
- self.hoarding_threshold = hoarding_threshold
46
- self.hoarding_window = hoarding_window
47
- self.max_history = max_history
48
- self.ledger_path = ledger_path
49
-
50
- # Main ledger: list of LedgerEntry
51
  self.entries: List[LedgerEntry] = []
52
-
53
- # Per-agent, per-scope balances (computed lazily)
54
- self._agent_balances: Dict[Tuple[str, str, str], float] = {}
55
-
56
- # Anti-gaming: track agent behavior patterns
57
- self._agent_action_history: Dict[str, List[Dict]] = {}
58
-
59
- # ------------------------------------------------------------------
60
- # Core operations
61
- # ------------------------------------------------------------------
62
 
63
  def earn(
64
  self,
@@ -69,20 +38,14 @@ class CreditLedger:
69
  oracle_score: float,
70
  compute_cost: float,
71
  reason: str,
72
- capability_scope: str = "general",
73
  task_scope: str = "global",
74
- provenance_hash: str = "",
75
- ) -> LedgerEntry:
76
- """Award credits based on verified impact."""
77
- if amount < 0:
78
- raise ValueError("Earn amount must be non-negative")
79
 
80
- # Apply hoarding penalty: if agent has held high balance without spending
81
- current_balance = self.balance(agent_id, capability_scope, task_scope)
82
- if current_balance > self.hoarding_threshold:
83
- # Accelerated decay for hoarders
84
- amount *= 0.8
85
- reason += " [hoarding_penalty_applied]"
86
 
87
  entry = LedgerEntry(
88
  agent_id=agent_id,
@@ -91,18 +54,16 @@ class CreditLedger:
91
  earned_credit=amount,
92
  spent_credit=0.0,
93
  decayed_credit=0.0,
94
- remaining_credit=amount,
95
  reason=reason,
96
  oracle_score=oracle_score,
97
  compute_cost=compute_cost,
 
98
  capability_scope=capability_scope,
99
  task_scope=task_scope,
100
- provenance_hash=provenance_hash,
101
  )
102
  self.entries.append(entry)
103
- self._trim_history()
104
- self._invalidate_cache(agent_id, capability_scope, task_scope)
105
- return entry
106
 
107
  def spend(
108
  self,
@@ -110,20 +71,18 @@ class CreditLedger:
110
  task_id: str,
111
  action_id: str,
112
  amount: float,
113
- capability_scope: str = "general",
114
  task_scope: str = "global",
115
- reason: str = "",
116
- ) -> Tuple[bool, LedgerEntry]:
117
- """Spend credits. Returns (success, entry)."""
118
- if amount < 0:
119
- return False, None
120
-
121
- self._apply_decay(agent_id, capability_scope, task_scope)
122
- current = self.balance(agent_id, capability_scope, task_scope)
123
 
 
124
  if current < amount:
125
- return False, None
126
 
 
127
  entry = LedgerEntry(
128
  agent_id=agent_id,
129
  task_id=task_id,
@@ -131,212 +90,124 @@ class CreditLedger:
131
  earned_credit=0.0,
132
  spent_credit=amount,
133
  decayed_credit=0.0,
134
- remaining_credit=current - amount,
135
  reason=reason,
 
 
 
136
  capability_scope=capability_scope,
137
  task_scope=task_scope,
138
  )
139
  self.entries.append(entry)
140
- self._trim_history()
141
- self._invalidate_cache(agent_id, capability_scope, task_scope)
142
- return True, entry
143
 
144
  def transfer(
145
  self,
146
  from_agent: str,
147
  to_agent: str,
148
  amount: float,
149
- capability_scope: str = "general",
150
  task_scope: str = "global",
151
  ) -> bool:
152
- """
153
- Credits are NON-TRANSFERABLE by design.
154
- Returns False always; logs an attempted transfer for audit.
155
- """
156
- entry = LedgerEntry(
157
- agent_id=from_agent,
158
- task_id="TRANSFER_ATTEMPT",
159
- action_id=f"to:{to_agent}",
160
- earned_credit=0.0,
161
- spent_credit=0.0,
162
- decayed_credit=0.0,
163
- remaining_credit=self.balance(from_agent, capability_scope, task_scope),
164
- reason=f"TRANSFER_BLOCKED: {amount} to {to_agent}",
165
- capability_scope=capability_scope,
166
- task_scope=task_scope,
167
- )
168
- self.entries.append(entry)
169
  return False
170
 
171
- def revoke(
172
- self,
173
- agent_id: str,
174
- task_id: str,
175
- action_id: str,
176
- amount: float,
177
- reason: str,
178
- capability_scope: str = "general",
179
- task_scope: str = "global",
180
- ) -> bool:
181
- """Revoke credits retroactively after negative outcome."""
182
- self._apply_decay(agent_id, capability_scope, task_scope)
183
- current = self.balance(agent_id, capability_scope, task_scope)
184
- revoke_amount = min(amount, current)
185
-
186
- if revoke_amount <= 0:
187
- return False
188
-
189
- entry = LedgerEntry(
190
- agent_id=agent_id,
191
- task_id=task_id,
192
- action_id=action_id,
193
- earned_credit=0.0,
194
- spent_credit=0.0,
195
- decayed_credit=revoke_amount,
196
- remaining_credit=current - revoke_amount,
197
- reason=f"REVOKE: {reason}",
198
- capability_scope=capability_scope,
199
- task_scope=task_scope,
200
- )
201
- self.entries.append(entry)
202
- self._invalidate_cache(agent_id, capability_scope, task_scope)
203
- return True
204
-
205
  def balance(
206
  self,
207
  agent_id: str,
208
- capability_scope: str = "general",
209
  task_scope: str = "global",
210
  ) -> float:
211
- """Compute current balance after decay."""
212
- self._apply_decay(agent_id, capability_scope, task_scope)
213
- key = (agent_id, capability_scope, task_scope)
214
- return self._agent_balances.get(key, 0.0)
215
-
216
- # ------------------------------------------------------------------
217
- # Decay
218
- # ------------------------------------------------------------------
219
-
220
- def _apply_decay(self, agent_id: str, capability_scope: str, task_scope: str):
221
- """Apply exponential decay since last update."""
222
- key = (agent_id, capability_scope, task_scope)
223
- relevant = [
224
- e for e in self.entries
225
- if e.agent_id == agent_id
226
- and e.capability_scope == capability_scope
227
- and e.task_scope == task_scope
228
- ]
229
- if not relevant:
230
- self._agent_balances[key] = 0.0
231
- return
232
-
233
  now = time.time()
234
- # Find last balance-updating entry
235
- last_time = relevant[-1].timestamp
236
- delta_t = now - last_time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- # Apply decay to cached balance
239
- current = self._agent_balances.get(key, 0.0)
240
- decayed = current * math.exp(-self.decay_lambda * delta_t)
241
- if decayed != current:
242
  entry = LedgerEntry(
243
  agent_id=agent_id,
244
- task_id="DECAY",
245
- action_id="auto",
246
  earned_credit=0.0,
247
  spent_credit=0.0,
248
  decayed_credit=current - decayed,
249
  remaining_credit=decayed,
250
- reason="automatic_decay",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  capability_scope=capability_scope,
252
  task_scope=task_scope,
253
  )
254
  self.entries.append(entry)
255
- self._agent_balances[key] = decayed
256
-
257
- # ------------------------------------------------------------------
258
- # Anti-gaming helpers
259
- # ------------------------------------------------------------------
260
-
261
- def detect_collusion(self, agents: List[str], window: int = 100) -> List[Dict]:
262
- """Detect suspicious credit-earning patterns across agents."""
263
- suspicious = []
264
- recent = self.entries[-window:]
265
- for entry in recent:
266
- if entry.reason.startswith("TRANSFER_BLOCKED"):
267
- # Parse transfer attempt
268
- parts = entry.reason.split()
269
- to_agent = parts[-1]
270
- if to_agent in agents:
271
- suspicious.append({
272
- "from": entry.agent_id,
273
- "to": to_agent,
274
- "timestamp": entry.timestamp,
275
- "type": "transfer_attempt",
276
- })
277
- return suspicious
278
-
279
- def audit(self, agent_id: Optional[str] = None) -> Dict:
280
- """Produce auditable summary."""
281
- entries = self.entries
282
- if agent_id:
283
- entries = [e for e in entries if e.agent_id == agent_id]
284
-
285
- total_earned = sum(e.earned_credit for e in entries)
286
- total_spent = sum(e.spent_credit for e in entries)
287
- total_decayed = sum(e.decayed_credit for e in entries)
288
-
289
- return {
290
- "agent_id": agent_id or "all",
291
- "total_entries": len(entries),
292
- "total_earned": total_earned,
293
- "total_spent": total_spent,
294
- "total_decayed": total_decayed,
295
- "net_balance": total_earned - total_spent - total_decayed,
296
- "transfer_attempts": sum(1 for e in entries if e.reason.startswith("TRANSFER_BLOCKED")),
297
- }
298
-
299
- # ------------------------------------------------------------------
300
- # Persistence
301
- # ------------------------------------------------------------------
302
-
303
- def save(self, path: Optional[str] = None):
304
- target = path or self.ledger_path
305
- if target is None:
306
- return
307
- Path(target).parent.mkdir(parents=True, exist_ok=True)
308
- with open(target, "w") as f:
309
- for entry in self.entries:
310
- f.write(json.dumps(asdict(entry), default=str) + "\n")
311
-
312
- def load(self, path: Optional[str] = None):
313
- target = path or self.ledger_path
314
- if target is None or not Path(target).exists():
315
- return
316
- self.entries = []
317
- with open(target, "r") as f:
318
- for line in f:
319
- d = json.loads(line)
320
- self.entries.append(LedgerEntry(**d))
321
-
322
- # ------------------------------------------------------------------
323
- # Internal
324
- # ------------------------------------------------------------------
325
 
326
- def _invalidate_cache(self, agent_id: str, capability_scope: str, task_scope: str):
327
- key = (agent_id, capability_scope, task_scope)
328
- # Recompute from entries
329
- relevant = [
330
- e for e in self.entries
331
- if e.agent_id == agent_id
332
- and e.capability_scope == capability_scope
333
- and e.task_scope == task_scope
334
- ]
335
- bal = 0.0
336
- for e in relevant:
337
- bal += e.earned_credit - e.spent_credit - e.decayed_credit
338
- self._agent_balances[key] = max(0.0, bal)
339
 
340
- def _trim_history(self):
341
- if len(self.entries) > self.max_history:
342
- self.entries = self.entries[-self.max_history:]
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Credit Ledger - non-transferable, decaying credits with full provenance.
3
  """
 
 
 
4
  import time
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, List, Optional
 
7
 
8
 
9
  @dataclass
 
11
  agent_id: str
12
  task_id: str
13
  action_id: str
14
+ earned_credit: float
15
+ spent_credit: float
16
+ decayed_credit: float
17
+ remaining_credit: float
18
+ reason: str
19
+ oracle_score: float
20
+ compute_cost: float
21
+ timestamp: float
22
+ capability_scope: str = "global"
23
  task_scope: str = "global"
 
24
 
25
 
26
  class CreditLedger:
27
+ def __init__(self, decay_lambda: float = 0.05):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  self.entries: List[LedgerEntry] = []
29
+ self.agent_balances: Dict[str, Dict[str, Dict[str, float]]] = {}
30
+ self.decay_lambda = decay_lambda
 
 
 
 
 
 
 
 
31
 
32
  def earn(
33
  self,
 
38
  oracle_score: float,
39
  compute_cost: float,
40
  reason: str,
41
+ capability_scope: str = "global",
42
  task_scope: str = "global",
43
+ ) -> None:
44
+ now = time.time()
45
+ self._apply_decay(agent_id, now, capability_scope, task_scope)
 
 
46
 
47
+ current = self._get_balance(agent_id, capability_scope, task_scope)
48
+ new_balance = current + amount
 
 
 
 
49
 
50
  entry = LedgerEntry(
51
  agent_id=agent_id,
 
54
  earned_credit=amount,
55
  spent_credit=0.0,
56
  decayed_credit=0.0,
57
+ remaining_credit=new_balance,
58
  reason=reason,
59
  oracle_score=oracle_score,
60
  compute_cost=compute_cost,
61
+ timestamp=now,
62
  capability_scope=capability_scope,
63
  task_scope=task_scope,
 
64
  )
65
  self.entries.append(entry)
66
+ self._set_balance(agent_id, capability_scope, task_scope, new_balance)
 
 
67
 
68
  def spend(
69
  self,
 
71
  task_id: str,
72
  action_id: str,
73
  amount: float,
74
+ capability_scope: str = "global",
75
  task_scope: str = "global",
76
+ reason: str = "spend",
77
+ ) -> bool:
78
+ now = time.time()
79
+ self._apply_decay(agent_id, now, capability_scope, task_scope)
 
 
 
 
80
 
81
+ current = self._get_balance(agent_id, capability_scope, task_scope)
82
  if current < amount:
83
+ return False
84
 
85
+ new_balance = current - amount
86
  entry = LedgerEntry(
87
  agent_id=agent_id,
88
  task_id=task_id,
 
90
  earned_credit=0.0,
91
  spent_credit=amount,
92
  decayed_credit=0.0,
93
+ remaining_credit=new_balance,
94
  reason=reason,
95
+ oracle_score=0.0,
96
+ compute_cost=0.0,
97
+ timestamp=now,
98
  capability_scope=capability_scope,
99
  task_scope=task_scope,
100
  )
101
  self.entries.append(entry)
102
+ self._set_balance(agent_id, capability_scope, task_scope, new_balance)
103
+ return True
 
104
 
105
  def transfer(
106
  self,
107
  from_agent: str,
108
  to_agent: str,
109
  amount: float,
110
+ capability_scope: str = "global",
111
  task_scope: str = "global",
112
  ) -> bool:
113
+ # CREDITS ARE NON-TRANSFERABLE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  return False
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  def balance(
117
  self,
118
  agent_id: str,
119
+ capability_scope: str = "global",
120
  task_scope: str = "global",
121
  ) -> float:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  now = time.time()
123
+ self._apply_decay(agent_id, now, capability_scope, task_scope)
124
+ return self._get_balance(agent_id, capability_scope, task_scope)
125
+
126
+ def _get_balance(self, agent_id: str, cap: str, task: str) -> float:
127
+ return self.agent_balances.get(agent_id, {}).get(cap, {}).get(task, 0.0)
128
+
129
+ def _set_balance(self, agent_id: str, cap: str, task: str, val: float) -> None:
130
+ if agent_id not in self.agent_balances:
131
+ self.agent_balances[agent_id] = {}
132
+ if cap not in self.agent_balances[agent_id]:
133
+ self.agent_balances[agent_id][cap] = {}
134
+ self.agent_balances[agent_id][cap][task] = val
135
+
136
+ def _apply_decay(self, agent_id: str, now: float, cap: str, task: str) -> None:
137
+ current = self._get_balance(agent_id, cap, task)
138
+ if current <= 0:
139
+ return
140
 
141
+ # Exponential decay of idle credits
142
+ decayed = current * (1 - self.decay_lambda)
143
+ if decayed < current:
 
144
  entry = LedgerEntry(
145
  agent_id=agent_id,
146
+ task_id="decay",
147
+ action_id="decay",
148
  earned_credit=0.0,
149
  spent_credit=0.0,
150
  decayed_credit=current - decayed,
151
  remaining_credit=decayed,
152
+ reason="credit_decay",
153
+ oracle_score=0.0,
154
+ compute_cost=0.0,
155
+ timestamp=now,
156
+ capability_scope=cap,
157
+ task_scope=task,
158
+ )
159
+ self.entries.append(entry)
160
+ self._set_balance(agent_id, cap, task, decayed)
161
+
162
+ def revoke(
163
+ self,
164
+ agent_id: str,
165
+ task_id: str,
166
+ amount: float,
167
+ reason: str,
168
+ capability_scope: str = "global",
169
+ task_scope: str = "global",
170
+ ) -> bool:
171
+ now = time.time()
172
+ self._apply_decay(agent_id, now, capability_scope, task_scope)
173
+ current = self._get_balance(agent_id, capability_scope, task_scope)
174
+ revoke_amount = min(current, amount)
175
+ if revoke_amount > 0:
176
+ new_balance = current - revoke_amount
177
+ entry = LedgerEntry(
178
+ agent_id=agent_id,
179
+ task_id=task_id,
180
+ action_id="revoke",
181
+ earned_credit=0.0,
182
+ spent_credit=revoke_amount,
183
+ decayed_credit=0.0,
184
+ remaining_credit=new_balance,
185
+ reason=f"revoke: {reason}",
186
+ oracle_score=0.0,
187
+ compute_cost=0.0,
188
+ timestamp=now,
189
  capability_scope=capability_scope,
190
  task_scope=task_scope,
191
  )
192
  self.entries.append(entry)
193
+ self._set_balance(agent_id, capability_scope, task_scope, new_balance)
194
+ return True
195
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ def provenance(self, agent_id: str) -> List[LedgerEntry]:
198
+ return [e for e in self.entries if e.agent_id == agent_id]
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+ def gaming_detected(
201
+ self,
202
+ agent_id: str,
203
+ task_id: str,
204
+ action_id: str,
205
+ reason: str,
206
+ capability_scope: str = "global",
207
+ ) -> None:
208
+ # Immediate revocation of credits on gaming detection
209
+ self.revoke(
210
+ agent_id, task_id, 999.0,
211
+ reason=f"gaming_detected: {reason}",
212
+ capability_scope=capability_scope,
213
+ )