the-grid-cc 1.1.0

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.
package/core/energy.py ADDED
@@ -0,0 +1,267 @@
1
+ """
2
+ Energy Management - Token/budget tracking for The Grid.
3
+
4
+ "Programs need Energy to run on The Grid."
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import Optional
10
+ from enum import Enum
11
+ import threading
12
+
13
+
14
+ class EnergyLevel(Enum):
15
+ """Energy level thresholds."""
16
+ FULL = "full" # > 80%
17
+ NORMAL = "normal" # 40-80%
18
+ LOW = "low" # 10-40%
19
+ CRITICAL = "critical" # < 10%
20
+
21
+
22
+ @dataclass
23
+ class EnergyTransaction:
24
+ """Record of energy consumption or allocation."""
25
+ timestamp: datetime
26
+ entity_id: str
27
+ entity_type: str # grid, cluster, block, thread, program
28
+ amount: int
29
+ action: str # allocated, consumed, returned
30
+ balance_after: int
31
+ description: str = ""
32
+
33
+
34
+ class EnergyPool:
35
+ """An energy pool for a Grid entity."""
36
+
37
+ def __init__(self, budget: int, name: str, parent: Optional["EnergyPool"] = None):
38
+ self.budget = budget
39
+ self.name = name
40
+ self.parent = parent
41
+ self.consumed = 0
42
+ self.allocated_to_children = 0
43
+ self.transactions: list[EnergyTransaction] = []
44
+ self._lock = threading.Lock()
45
+
46
+ @property
47
+ def available(self) -> int:
48
+ """Get available energy (not consumed or allocated to children)."""
49
+ return self.budget - self.consumed - self.allocated_to_children
50
+
51
+ @property
52
+ def remaining(self) -> int:
53
+ """Get remaining energy (budget minus consumed)."""
54
+ return self.budget - self.consumed
55
+
56
+ @property
57
+ def level(self) -> EnergyLevel:
58
+ """Get current energy level."""
59
+ pct = self.percentage
60
+ if pct > 80:
61
+ return EnergyLevel.FULL
62
+ elif pct > 40:
63
+ return EnergyLevel.NORMAL
64
+ elif pct > 10:
65
+ return EnergyLevel.LOW
66
+ else:
67
+ return EnergyLevel.CRITICAL
68
+
69
+ @property
70
+ def percentage(self) -> float:
71
+ """Get energy as percentage of budget."""
72
+ if self.budget == 0:
73
+ return 0.0
74
+ return (self.remaining / self.budget) * 100
75
+
76
+ def consume(self, amount: int, entity_id: str, description: str = "") -> bool:
77
+ """Consume energy from this pool."""
78
+ with self._lock:
79
+ if amount > self.available:
80
+ return False
81
+ self.consumed += amount
82
+ self._record_transaction(
83
+ entity_id=entity_id,
84
+ entity_type="consumer",
85
+ amount=amount,
86
+ action="consumed",
87
+ description=description,
88
+ )
89
+ return True
90
+
91
+ def allocate_to_child(self, amount: int, child_name: str) -> Optional["EnergyPool"]:
92
+ """Allocate energy to a child pool."""
93
+ with self._lock:
94
+ if amount > self.available:
95
+ return None
96
+ self.allocated_to_children += amount
97
+ child_pool = EnergyPool(budget=amount, name=child_name, parent=self)
98
+ self._record_transaction(
99
+ entity_id=child_name,
100
+ entity_type="child_pool",
101
+ amount=amount,
102
+ action="allocated",
103
+ description=f"Allocated to {child_name}",
104
+ )
105
+ return child_pool
106
+
107
+ def return_unused(self) -> int:
108
+ """Return unused energy to parent pool."""
109
+ if not self.parent:
110
+ return 0
111
+ with self._lock:
112
+ unused = self.available
113
+ if unused > 0:
114
+ self.parent.allocated_to_children -= unused
115
+ self.budget -= unused
116
+ self._record_transaction(
117
+ entity_id=self.name,
118
+ entity_type="pool",
119
+ amount=unused,
120
+ action="returned",
121
+ description=f"Returned to {self.parent.name}",
122
+ )
123
+ return unused
124
+
125
+ def _record_transaction(
126
+ self,
127
+ entity_id: str,
128
+ entity_type: str,
129
+ amount: int,
130
+ action: str,
131
+ description: str = "",
132
+ ) -> None:
133
+ """Record an energy transaction."""
134
+ self.transactions.append(EnergyTransaction(
135
+ timestamp=datetime.now(),
136
+ entity_id=entity_id,
137
+ entity_type=entity_type,
138
+ amount=amount,
139
+ action=action,
140
+ balance_after=self.remaining,
141
+ description=description,
142
+ ))
143
+
144
+ def get_bar(self, width: int = 20) -> str:
145
+ """Get a visual energy bar."""
146
+ filled = int((self.percentage / 100) * width)
147
+ empty = width - filled
148
+ return "█" * filled + "░" * empty
149
+
150
+ def summary(self) -> str:
151
+ """Get energy summary."""
152
+ return (
153
+ f"{self.name}: {self.get_bar()} "
154
+ f"{self.remaining:,}/{self.budget:,} ({self.percentage:.0f}%)"
155
+ )
156
+
157
+
158
+ class EnergyManager:
159
+ """
160
+ Manages energy (token budgets) across The Grid.
161
+
162
+ Energy flows:
163
+ Grid → Cluster → Block → Thread/Program
164
+
165
+ Each level can allocate energy to children but cannot
166
+ exceed its own budget.
167
+ """
168
+
169
+ def __init__(
170
+ self,
171
+ grid_budget: int = 10000,
172
+ cluster_budget: int = 2000,
173
+ block_budget: int = 500,
174
+ thread_budget: int = 100,
175
+ spawn_cost: int = 100,
176
+ low_threshold: float = 0.1,
177
+ ):
178
+ self.grid_budget = grid_budget
179
+ self.cluster_budget = cluster_budget
180
+ self.block_budget = block_budget
181
+ self.thread_budget = thread_budget
182
+ self.spawn_cost = spawn_cost
183
+ self.low_threshold = low_threshold
184
+
185
+ # Root energy pool
186
+ self.grid_pool = EnergyPool(budget=grid_budget, name="Grid")
187
+
188
+ # Track all pools
189
+ self.pools: dict[str, EnergyPool] = {"grid": self.grid_pool}
190
+
191
+ def create_cluster_pool(self, cluster_id: str) -> Optional[EnergyPool]:
192
+ """Create an energy pool for a new Cluster."""
193
+ pool = self.grid_pool.allocate_to_child(
194
+ amount=min(self.cluster_budget, self.grid_pool.available),
195
+ child_name=f"cluster:{cluster_id}",
196
+ )
197
+ if pool:
198
+ self.pools[f"cluster:{cluster_id}"] = pool
199
+ return pool
200
+
201
+ def create_block_pool(self, cluster_id: str, block_id: str) -> Optional[EnergyPool]:
202
+ """Create an energy pool for a new Block."""
203
+ cluster_pool = self.pools.get(f"cluster:{cluster_id}")
204
+ if not cluster_pool:
205
+ return None
206
+ pool = cluster_pool.allocate_to_child(
207
+ amount=min(self.block_budget, cluster_pool.available),
208
+ child_name=f"block:{block_id}",
209
+ )
210
+ if pool:
211
+ self.pools[f"block:{block_id}"] = pool
212
+ return pool
213
+
214
+ def create_thread_pool(self, block_id: str, thread_id: str) -> Optional[EnergyPool]:
215
+ """Create an energy pool for a new Thread."""
216
+ block_pool = self.pools.get(f"block:{block_id}")
217
+ if not block_pool:
218
+ return None
219
+ pool = block_pool.allocate_to_child(
220
+ amount=min(self.thread_budget, block_pool.available),
221
+ child_name=f"thread:{thread_id}",
222
+ )
223
+ if pool:
224
+ self.pools[f"thread:{thread_id}"] = pool
225
+ return pool
226
+
227
+ def consume(self, pool_id: str, amount: int, description: str = "") -> bool:
228
+ """Consume energy from a pool."""
229
+ pool = self.pools.get(pool_id)
230
+ if not pool:
231
+ return False
232
+ return pool.consume(amount, pool_id, description)
233
+
234
+ def get_pool(self, pool_id: str) -> Optional[EnergyPool]:
235
+ """Get a pool by ID."""
236
+ return self.pools.get(pool_id)
237
+
238
+ def is_low(self, pool_id: str) -> bool:
239
+ """Check if a pool is at low energy."""
240
+ pool = self.pools.get(pool_id)
241
+ if not pool:
242
+ return True
243
+ return pool.percentage <= (self.low_threshold * 100)
244
+
245
+ def grid_summary(self) -> str:
246
+ """Get full grid energy summary."""
247
+ lines = ["ENERGY FLOW", "═" * 40]
248
+ lines.append(self.grid_pool.summary())
249
+
250
+ # Show cluster pools
251
+ for pool_id, pool in self.pools.items():
252
+ if pool_id.startswith("cluster:"):
253
+ lines.append(f" └─ {pool.summary()}")
254
+ # Show block pools under this cluster
255
+ for block_id, block_pool in self.pools.items():
256
+ if block_id.startswith("block:") and block_pool.parent == pool:
257
+ lines.append(f" ├─ {block_pool.summary()}")
258
+
259
+ return "\n".join(lines)
260
+
261
+ def total_consumed(self) -> int:
262
+ """Get total energy consumed across all pools."""
263
+ return sum(pool.consumed for pool in self.pools.values())
264
+
265
+ def total_remaining(self) -> int:
266
+ """Get total remaining energy in grid."""
267
+ return self.grid_pool.remaining
package/core/grid.py ADDED
@@ -0,0 +1,326 @@
1
+ """
2
+ The Grid - Main orchestration class.
3
+
4
+ "The Grid. A digital frontier. I tried to picture clusters of information
5
+ as they moved through the computer."
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Optional, Any
12
+ from enum import Enum
13
+ import uuid
14
+ import yaml
15
+ import json
16
+
17
+ from .cluster import Cluster, ClusterStatus
18
+ from .block import Block, BlockStatus
19
+ from .thread import Thread, ThreadStatus
20
+ from .program import Program, ProgramStatus
21
+ from .recognizer import Recognizer, RecognizerType
22
+ from .disc import IdentityDisc
23
+ from .energy import EnergyManager, EnergyPool
24
+ from .io_tower import IOTower, CheckpointReason, CheckpointContext
25
+
26
+
27
+ class GridStatus(Enum):
28
+ """Status of The Grid."""
29
+ INITIALIZING = "initializing"
30
+ READY = "ready"
31
+ RUNNING = "running"
32
+ PAUSED = "paused"
33
+ COMPLETED = "completed"
34
+ FAILED = "failed"
35
+
36
+
37
+ @dataclass
38
+ class GridConfig:
39
+ """Configuration for The Grid."""
40
+ # Fission control
41
+ max_depth: int = 4
42
+ max_concurrent: int = 8
43
+ energy_per_spawn: int = 100
44
+
45
+ # Energy budgets
46
+ grid_budget: int = 10000
47
+ cluster_budget: int = 2000
48
+ block_budget: int = 500
49
+ thread_budget: int = 100
50
+ low_energy_threshold: float = 0.1
51
+
52
+ # I/O Tower
53
+ require_at_depth: int = 3
54
+ require_before_commit: bool = True
55
+ require_on_error: bool = True
56
+ timeout_seconds: int = 300
57
+
58
+ # Display
59
+ tron_aesthetics: bool = True
60
+
61
+ @classmethod
62
+ def from_yaml(cls, path: Path) -> "GridConfig":
63
+ """Load config from YAML file."""
64
+ with open(path) as f:
65
+ data = yaml.safe_load(f)
66
+
67
+ fission = data.get("fission", {})
68
+ energy = data.get("energy", {})
69
+ io = data.get("io_towers", {})
70
+ display = data.get("display", {})
71
+
72
+ return cls(
73
+ max_depth=fission.get("max_depth", 4),
74
+ max_concurrent=fission.get("max_concurrent", 8),
75
+ energy_per_spawn=fission.get("energy_per_spawn", 100),
76
+ grid_budget=energy.get("grid_budget", 10000),
77
+ cluster_budget=energy.get("cluster_budget", 2000),
78
+ block_budget=energy.get("block_budget", 500),
79
+ thread_budget=energy.get("thread_budget", 100),
80
+ low_energy_threshold=energy.get("low_energy_threshold", 0.1),
81
+ require_at_depth=io.get("require_at_depth", 3),
82
+ require_before_commit=io.get("require_before_commit", True),
83
+ require_on_error=io.get("require_on_error", True),
84
+ timeout_seconds=io.get("timeout_seconds", 300),
85
+ tron_aesthetics=display.get("tron_aesthetics", True),
86
+ )
87
+
88
+
89
+ class Grid:
90
+ """
91
+ The Grid - Main orchestration class.
92
+
93
+ The Grid is the digital frontier where Clusters, Blocks, Threads,
94
+ and Programs execute. It manages the entire execution hierarchy
95
+ and coordinates energy flow.
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ name: str = "The Grid",
101
+ config: GridConfig = None,
102
+ config_path: Path = None,
103
+ ):
104
+ self.id = str(uuid.uuid4())[:8]
105
+ self.name = name
106
+ self.status = GridStatus.INITIALIZING
107
+ self.created_at = datetime.now()
108
+
109
+ # Load configuration
110
+ if config:
111
+ self.config = config
112
+ elif config_path and config_path.exists():
113
+ self.config = GridConfig.from_yaml(config_path)
114
+ else:
115
+ self.config = GridConfig()
116
+
117
+ # Energy management
118
+ self.energy = EnergyManager(
119
+ grid_budget=self.config.grid_budget,
120
+ cluster_budget=self.config.cluster_budget,
121
+ block_budget=self.config.block_budget,
122
+ thread_budget=self.config.thread_budget,
123
+ spawn_cost=self.config.energy_per_spawn,
124
+ low_threshold=self.config.low_energy_threshold,
125
+ )
126
+
127
+ # I/O Tower (human checkpoints)
128
+ self.io_tower = IOTower(
129
+ require_at_depth=self.config.require_at_depth,
130
+ require_before_commit=self.config.require_before_commit,
131
+ require_on_error=self.config.require_on_error,
132
+ timeout_seconds=self.config.timeout_seconds,
133
+ )
134
+
135
+ # Clusters
136
+ self.clusters: dict[str, Cluster] = {}
137
+
138
+ # Active Programs (for tracking concurrent execution)
139
+ self.active_programs: dict[str, Program] = {}
140
+
141
+ # Statistics
142
+ self.cycles_completed = 0
143
+ self.programs_spawned = 0
144
+ self.programs_derezzed = 0
145
+
146
+ self.status = GridStatus.READY
147
+
148
+ def create_cluster(
149
+ self,
150
+ name: str,
151
+ purpose: str,
152
+ description: str = "",
153
+ ) -> Cluster:
154
+ """Create a new Cluster on The Grid."""
155
+ cluster = Cluster(
156
+ name=name,
157
+ purpose=purpose,
158
+ description=description,
159
+ )
160
+
161
+ # Allocate energy
162
+ cluster.energy_pool = self.energy.create_cluster_pool(cluster.id)
163
+
164
+ self.clusters[cluster.id] = cluster
165
+ return cluster
166
+
167
+ def get_cluster(self, cluster_id: str) -> Optional[Cluster]:
168
+ """Get a Cluster by ID."""
169
+ return self.clusters.get(cluster_id)
170
+
171
+ def get_cluster_by_name(self, name: str) -> Optional[Cluster]:
172
+ """Get a Cluster by name."""
173
+ for cluster in self.clusters.values():
174
+ if cluster.name == name:
175
+ return cluster
176
+ return None
177
+
178
+ def spawn_program(
179
+ self,
180
+ name: str,
181
+ purpose: str,
182
+ parent: Program = None,
183
+ energy_budget: int = None,
184
+ ) -> Optional[Program]:
185
+ """Spawn a new Program on The Grid."""
186
+ if energy_budget is None:
187
+ energy_budget = self.config.energy_per_spawn
188
+
189
+ # Check concurrent limit
190
+ if len(self.active_programs) >= self.config.max_concurrent:
191
+ return None
192
+
193
+ # Check depth limit if parent exists
194
+ depth = 0 if parent is None else parent.depth + 1
195
+ if depth > self.config.max_depth:
196
+ return None
197
+
198
+ # Check if I/O Tower checkpoint needed
199
+ if self.io_tower.should_checkpoint(CheckpointReason.FISSION_DEPTH, depth):
200
+ context = CheckpointContext(
201
+ reason=CheckpointReason.FISSION_DEPTH,
202
+ program_id=parent.id if parent else "grid",
203
+ program_name=parent.name if parent else "Grid",
204
+ depth=depth,
205
+ energy_remaining=parent.disc.energy_remaining() if parent else self.energy.total_remaining(),
206
+ energy_allocated=parent.disc.energy_allocated if parent else self.config.grid_budget,
207
+ pending_actions=[f"Spawn Program: {name}"],
208
+ details={"purpose": purpose, "energy_cost": energy_budget},
209
+ )
210
+ # In actual use, this would pause for user input
211
+ # For now, we continue (checkpoint is logged)
212
+ self.io_tower.checkpoint(context)
213
+
214
+ program = Program(
215
+ name=name,
216
+ purpose=purpose,
217
+ parent=parent,
218
+ energy_budget=energy_budget,
219
+ )
220
+
221
+ self.active_programs[program.id] = program
222
+ self.programs_spawned += 1
223
+
224
+ return program
225
+
226
+ def derez_program(self, program: Program) -> None:
227
+ """Derez (terminate) a Program."""
228
+ program.derez()
229
+ if program.id in self.active_programs:
230
+ del self.active_programs[program.id]
231
+ self.programs_derezzed += 1
232
+
233
+ def start(self) -> None:
234
+ """Start Grid execution."""
235
+ self.status = GridStatus.RUNNING
236
+
237
+ # Start all pending clusters
238
+ for cluster in self.clusters.values():
239
+ if cluster.status == ClusterStatus.PENDING:
240
+ cluster.start()
241
+
242
+ def pause(self) -> None:
243
+ """Pause Grid execution."""
244
+ self.status = GridStatus.PAUSED
245
+
246
+ def resume(self) -> None:
247
+ """Resume Grid execution."""
248
+ if self.status == GridStatus.PAUSED:
249
+ self.status = GridStatus.RUNNING
250
+
251
+ def complete_cycle(self) -> None:
252
+ """Complete a cycle of execution."""
253
+ self.cycles_completed += 1
254
+
255
+ def is_complete(self) -> bool:
256
+ """Check if all work on The Grid is complete."""
257
+ return all(
258
+ c.status in (ClusterStatus.COMPLETED, ClusterStatus.DEREZZED)
259
+ for c in self.clusters.values()
260
+ )
261
+
262
+ def get_status_summary(self) -> dict:
263
+ """Get Grid status summary."""
264
+ return {
265
+ "id": self.id,
266
+ "name": self.name,
267
+ "status": self.status.value,
268
+ "clusters": len(self.clusters),
269
+ "active_programs": len(self.active_programs),
270
+ "programs_spawned": self.programs_spawned,
271
+ "programs_derezzed": self.programs_derezzed,
272
+ "cycles_completed": self.cycles_completed,
273
+ "energy_remaining": self.energy.total_remaining(),
274
+ "energy_consumed": self.energy.total_consumed(),
275
+ }
276
+
277
+ def energy_percentage(self) -> float:
278
+ """Get Grid energy as percentage."""
279
+ return self.energy.grid_pool.percentage
280
+
281
+ def get_energy_bar(self, width: int = 20) -> str:
282
+ """Get visual energy bar."""
283
+ return self.energy.grid_pool.get_bar(width)
284
+
285
+ def render_status(self) -> str:
286
+ """Render Grid status display."""
287
+ lines = [
288
+ "╔" + "═" * 77 + "╗",
289
+ "║" + " " * 77 + "║",
290
+ f"║ GRID STATUS {self.get_energy_bar()} {self.energy_percentage():.0f}% Energy" + " " * 22 + "║",
291
+ f"║ Active Programs {len(self.active_programs):<54}║",
292
+ f"║ Clusters {len(self.clusters):<54}║",
293
+ f"║ Cycles completed {self.cycles_completed:<54}║",
294
+ "║" + " " * 77 + "║",
295
+ ]
296
+ return "\n".join(lines)
297
+
298
+ def to_dict(self) -> dict:
299
+ """Serialize Grid to dictionary."""
300
+ return {
301
+ "id": self.id,
302
+ "name": self.name,
303
+ "status": self.status.value,
304
+ "created_at": self.created_at.isoformat(),
305
+ "config": {
306
+ "max_depth": self.config.max_depth,
307
+ "max_concurrent": self.config.max_concurrent,
308
+ "grid_budget": self.config.grid_budget,
309
+ },
310
+ "clusters": [c.to_dict() for c in self.clusters.values()],
311
+ "active_programs": [p.to_dict() for p in self.active_programs.values()],
312
+ "statistics": {
313
+ "cycles_completed": self.cycles_completed,
314
+ "programs_spawned": self.programs_spawned,
315
+ "programs_derezzed": self.programs_derezzed,
316
+ "energy_remaining": self.energy.total_remaining(),
317
+ "energy_consumed": self.energy.total_consumed(),
318
+ },
319
+ }
320
+
321
+ def to_json(self) -> str:
322
+ """Serialize Grid to JSON."""
323
+ return json.dumps(self.to_dict(), indent=2)
324
+
325
+ def __repr__(self) -> str:
326
+ return f"Grid(id={self.id}, name={self.name}, clusters={len(self.clusters)})"