algo-cli-runtime 0.14.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.
Files changed (237) hide show
  1. algo_cli/__init__.py +3 -0
  2. algo_cli/__main__.py +7 -0
  3. algo_cli/_internal/__init__.py +12 -0
  4. algo_cli/_internal/policy_chain.py +259 -0
  5. algo_cli/action_registry.py +1047 -0
  6. algo_cli/agent_blocks.py +550 -0
  7. algo_cli/agent_pipeline.py +1457 -0
  8. algo_cli/agent_threads.py +308 -0
  9. algo_cli/animations.py +316 -0
  10. algo_cli/cache_admission.py +209 -0
  11. algo_cli/capability_mask.py +66 -0
  12. algo_cli/chat_protocol.py +116 -0
  13. algo_cli/chatgpt_auth.py +510 -0
  14. algo_cli/chatgpt_client.py +657 -0
  15. algo_cli/code_rag.py +479 -0
  16. algo_cli/config.py +651 -0
  17. algo_cli/context_budget.py +679 -0
  18. algo_cli/credential_helpers.py +315 -0
  19. algo_cli/deliberation.py +29 -0
  20. algo_cli/display.py +1470 -0
  21. algo_cli/evals/__init__.py +21 -0
  22. algo_cli/evals/algorithm_effectiveness.py +560 -0
  23. algo_cli/evals/competitive_harness_rating.py +702 -0
  24. algo_cli/evals/cot_quality.py +220 -0
  25. algo_cli/evals/harness_retrieval_benchmark.py +401 -0
  26. algo_cli/evals/performance_regression.py +136 -0
  27. algo_cli/evals/scorecard_grading.py +308 -0
  28. algo_cli/evals/session_distribution.py +84 -0
  29. algo_cli/execution_guardrails.py +806 -0
  30. algo_cli/extensions_manifest.py +84 -0
  31. algo_cli/git_evidence.py +227 -0
  32. algo_cli/google_workspace.py +407 -0
  33. algo_cli/google_workspace_auth.py +523 -0
  34. algo_cli/harness.py +2587 -0
  35. algo_cli/identity.py +557 -0
  36. algo_cli/index_compute_lab.py +228 -0
  37. algo_cli/inference_harness.py +70 -0
  38. algo_cli/intelligence/__init__.py +1103 -0
  39. algo_cli/intelligence/acrobat_config.py +307 -0
  40. algo_cli/intelligence/acrobat_manifests.py +338 -0
  41. algo_cli/intelligence/acrobat_models.py +195 -0
  42. algo_cli/intelligence/acrobat_pipeline.py +295 -0
  43. algo_cli/intelligence/acrobat_runtime.py +302 -0
  44. algo_cli/intelligence/acrobat_security.py +261 -0
  45. algo_cli/intelligence/acrobat_workflows.py +226 -0
  46. algo_cli/intelligence/actionability.py +165 -0
  47. algo_cli/intelligence/adversarial_audit.py +136 -0
  48. algo_cli/intelligence/agent_arena.py +92 -0
  49. algo_cli/intelligence/agent_benchmark.py +236 -0
  50. algo_cli/intelligence/agent_runtime.py +171 -0
  51. algo_cli/intelligence/agents_as_tools.py +70 -0
  52. algo_cli/intelligence/artifact_binding.py +80 -0
  53. algo_cli/intelligence/autonomous_engineer.py +1976 -0
  54. algo_cli/intelligence/backpressure.py +99 -0
  55. algo_cli/intelligence/bloom_filter.py +186 -0
  56. algo_cli/intelligence/bonferroni.py +66 -0
  57. algo_cli/intelligence/boundary_compaction.py +98 -0
  58. algo_cli/intelligence/catalog_verifier.py +172 -0
  59. algo_cli/intelligence/cavecrew.py +118 -0
  60. algo_cli/intelligence/changelog.py +176 -0
  61. algo_cli/intelligence/checkpoint_resume.py +92 -0
  62. algo_cli/intelligence/circuit_breaker.py +88 -0
  63. algo_cli/intelligence/clarification_gate.py +101 -0
  64. algo_cli/intelligence/code_graph.py +180 -0
  65. algo_cli/intelligence/coderank.py +97 -0
  66. algo_cli/intelligence/consistent_hash.py +150 -0
  67. algo_cli/intelligence/consortium_synthesis.py +139 -0
  68. algo_cli/intelligence/construction/__init__.py +241 -0
  69. algo_cli/intelligence/construction/common.py +273 -0
  70. algo_cli/intelligence/construction/documents.py +496 -0
  71. algo_cli/intelligence/construction/labor_units.py +1395 -0
  72. algo_cli/intelligence/construction/payments.py +470 -0
  73. algo_cli/intelligence/construction/risk.py +784 -0
  74. algo_cli/intelligence/content_extractor.py +132 -0
  75. algo_cli/intelligence/context_adaptive.py +102 -0
  76. algo_cli/intelligence/context_ops.py +95 -0
  77. algo_cli/intelligence/count_min.py +145 -0
  78. algo_cli/intelligence/cow_state.py +103 -0
  79. algo_cli/intelligence/critic_loop.py +119 -0
  80. algo_cli/intelligence/cross_source.py +113 -0
  81. algo_cli/intelligence/daemon_mode.py +99 -0
  82. algo_cli/intelligence/dag_orchestration.py +151 -0
  83. algo_cli/intelligence/deep_research.py +155 -0
  84. algo_cli/intelligence/degenerate_detector.py +78 -0
  85. algo_cli/intelligence/delta_report.py +92 -0
  86. algo_cli/intelligence/discovery_event_log.py +92 -0
  87. algo_cli/intelligence/document_ingest.py +298 -0
  88. algo_cli/intelligence/dual_layer_validate.py +151 -0
  89. algo_cli/intelligence/echo_fidelity.py +73 -0
  90. algo_cli/intelligence/ema_tuning.py +104 -0
  91. algo_cli/intelligence/event_log.py +92 -0
  92. algo_cli/intelligence/evidence_graph.py +114 -0
  93. algo_cli/intelligence/extension_host.py +162 -0
  94. algo_cli/intelligence/extension_manifest.py +115 -0
  95. algo_cli/intelligence/falsification_suite.py +178 -0
  96. algo_cli/intelligence/finance/__init__.py +169 -0
  97. algo_cli/intelligence/finance/anomalies.py +135 -0
  98. algo_cli/intelligence/finance/ap_ar.py +351 -0
  99. algo_cli/intelligence/finance/cash.py +162 -0
  100. algo_cli/intelligence/finance/close.py +332 -0
  101. algo_cli/intelligence/finance/common.py +244 -0
  102. algo_cli/intelligence/finance/construction.py +135 -0
  103. algo_cli/intelligence/finance/controls.py +172 -0
  104. algo_cli/intelligence/finance/evidence.py +119 -0
  105. algo_cli/intelligence/finance/exceptions.py +157 -0
  106. algo_cli/intelligence/finance/reconciliations.py +254 -0
  107. algo_cli/intelligence/finance/revenue.py +109 -0
  108. algo_cli/intelligence/finance/tax.py +74 -0
  109. algo_cli/intelligence/finance/workpapers.py +111 -0
  110. algo_cli/intelligence/finding_record.py +120 -0
  111. algo_cli/intelligence/flow_dag.py +267 -0
  112. algo_cli/intelligence/gatherer.py +223 -0
  113. algo_cli/intelligence/golden_master.py +98 -0
  114. algo_cli/intelligence/graph_rag.py +195 -0
  115. algo_cli/intelligence/group_chat.py +143 -0
  116. algo_cli/intelligence/hash_dedup.py +145 -0
  117. algo_cli/intelligence/hyperloglog.py +128 -0
  118. algo_cli/intelligence/incremental_index.py +316 -0
  119. algo_cli/intelligence/index_store.py +16 -0
  120. algo_cli/intelligence/iteration_plan.py +133 -0
  121. algo_cli/intelligence/kernel_plugins.py +167 -0
  122. algo_cli/intelligence/lesson_catalog.py +135 -0
  123. algo_cli/intelligence/llm_fallback.py +169 -0
  124. algo_cli/intelligence/log2_histogram.py +267 -0
  125. algo_cli/intelligence/lsp_integration.py +147 -0
  126. algo_cli/intelligence/memory_evolution.py +117 -0
  127. algo_cli/intelligence/minhash_lsh.py +182 -0
  128. algo_cli/intelligence/multi_model_score.py +174 -0
  129. algo_cli/intelligence/multi_tier_grade.py +211 -0
  130. algo_cli/intelligence/negative_controls.py +113 -0
  131. algo_cli/intelligence/numeric_clamp.py +63 -0
  132. algo_cli/intelligence/occ_editor.py +66 -0
  133. algo_cli/intelligence/output_normalize.py +112 -0
  134. algo_cli/intelligence/parallel_delegation.py +98 -0
  135. algo_cli/intelligence/parallel_fanout.py +104 -0
  136. algo_cli/intelligence/permission_modes.py +105 -0
  137. algo_cli/intelligence/pre_push_gate.py +68 -0
  138. algo_cli/intelligence/prefetch.py +171 -0
  139. algo_cli/intelligence/process_framework.py +217 -0
  140. algo_cli/intelligence/project_graph.py +387 -0
  141. algo_cli/intelligence/query_expansion.py +146 -0
  142. algo_cli/intelligence/ralph_loop.py +117 -0
  143. algo_cli/intelligence/rate_limiter.py +153 -0
  144. algo_cli/intelligence/refactor_transaction.py +94 -0
  145. algo_cli/intelligence/research_workspace.py +108 -0
  146. algo_cli/intelligence/retraction_ledger.py +72 -0
  147. algo_cli/intelligence/saga_pattern.py +88 -0
  148. algo_cli/intelligence/session_fork.py +100 -0
  149. algo_cli/intelligence/shadow_editor.py +67 -0
  150. algo_cli/intelligence/shell_session.py +213 -0
  151. algo_cli/intelligence/source_registry.py +143 -0
  152. algo_cli/intelligence/spawn_scales.py +99 -0
  153. algo_cli/intelligence/stat_stability.py +104 -0
  154. algo_cli/intelligence/structural_validator.py +148 -0
  155. algo_cli/intelligence/subagent_spawner.py +111 -0
  156. algo_cli/intelligence/symmetric_verify.py +70 -0
  157. algo_cli/intelligence/task_classifier.py +129 -0
  158. algo_cli/intelligence/team_execution.py +122 -0
  159. algo_cli/intelligence/tiered_access.py +121 -0
  160. algo_cli/intelligence/utility_registry.py +159 -0
  161. algo_cli/intuition_engine.py +560 -0
  162. algo_cli/intuition_injector.py +82 -0
  163. algo_cli/kernels/__init__.py +5 -0
  164. algo_cli/kernels/manifest.py +763 -0
  165. algo_cli/main.py +3903 -0
  166. algo_cli/memory_candidates.py +541 -0
  167. algo_cli/memory_echo_veil.py +394 -0
  168. algo_cli/memory_runtime.py +112 -0
  169. algo_cli/model_info.py +548 -0
  170. algo_cli/model_profile.py +160 -0
  171. algo_cli/model_routing.py +74 -0
  172. algo_cli/oneshot.py +331 -0
  173. algo_cli/perf_telemetry.py +389 -0
  174. algo_cli/plugins.py +245 -0
  175. algo_cli/private_event_store.py +654 -0
  176. algo_cli/quantization/__init__.py +24 -0
  177. algo_cli/quantization/lloyd_max.py +98 -0
  178. algo_cli/quantization/turbo_quant.py +308 -0
  179. algo_cli/reasoning/__init__.py +46 -0
  180. algo_cli/reasoning/combinatorial.py +356 -0
  181. algo_cli/reasoning/graph_of_thought.py +297 -0
  182. algo_cli/reasoning/mcts.py +220 -0
  183. algo_cli/reasoning/neuro_symbolic.py +250 -0
  184. algo_cli/reasoning/react.py +246 -0
  185. algo_cli/reasoning/reflexion.py +225 -0
  186. algo_cli/reasoning/tree_of_thought.py +241 -0
  187. algo_cli/reasoning_bridge.py +150 -0
  188. algo_cli/reconciliation.py +284 -0
  189. algo_cli/reflex.py +385 -0
  190. algo_cli/resources/docs/ALGO.md +13958 -0
  191. algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
  192. algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
  193. algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
  194. algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
  195. algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
  196. algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
  197. algo_cli/resources/docs/main-split-map.md +35 -0
  198. algo_cli/resources/docs/privacy-and-context.md +48 -0
  199. algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
  200. algo_cli/resources/skills/README.md +26 -0
  201. algo_cli/resources/skills/algo-cli.md +59 -0
  202. algo_cli/resources/skills/edit-file-precision.md +49 -0
  203. algo_cli/resources/skills/harness-search-first.md +47 -0
  204. algo_cli/resources/skills/memory-recall-ritual.md +51 -0
  205. algo_cli/resources/skills/qol-algorithms.md +224 -0
  206. algo_cli/resources/skills/smart-error-recovery.md +56 -0
  207. algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
  208. algo_cli/retrieval_algorithms.py +127 -0
  209. algo_cli/runtime_qos.py +236 -0
  210. algo_cli/runtime_services.py +320 -0
  211. algo_cli/session_commands.py +95 -0
  212. algo_cli/session_mode.py +113 -0
  213. algo_cli/skills.py +430 -0
  214. algo_cli/slash_dispatch.py +1265 -0
  215. algo_cli/small_context.py +206 -0
  216. algo_cli/spawn_budget.py +89 -0
  217. algo_cli/task_ledger.py +84 -0
  218. algo_cli/task_router.py +197 -0
  219. algo_cli/tool_context.py +94 -0
  220. algo_cli/tool_contract.py +99 -0
  221. algo_cli/tool_policy.py +357 -0
  222. algo_cli/tool_runtime.py +647 -0
  223. algo_cli/tools.py +3056 -0
  224. algo_cli/url_scheme.py +174 -0
  225. algo_cli/verify.py +154 -0
  226. algo_cli/version_manifest.py +178 -0
  227. algo_cli/vision_screenshot_verify.py +76 -0
  228. algo_cli/workspace_resolver.py +68 -0
  229. algo_cli/x_account.py +209 -0
  230. algo_cli/xai_auth.py +374 -0
  231. algo_cli/xai_client.py +600 -0
  232. algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
  233. algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
  234. algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
  235. algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
  236. algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
  237. ollama_cli/__init__.py +67 -0
@@ -0,0 +1,153 @@
1
+ """Token Bucket Rate Limiter — smooth traffic shaping.
2
+
3
+ A token bucket fills at a fixed rate (tokens per second) up to a maximum
4
+ capacity (burst size). Each request consumes one or more tokens. If
5
+ insufficient tokens are available, the request is denied (or delayed).
6
+
7
+ Harness use:
8
+ - Throttle Ollama API calls to avoid rate limits
9
+ - Throttle embedding batches
10
+ - Throttle web fetches
11
+ - Throttle harness indexing rate
12
+
13
+ The leaky bucket variant (output rate constant) is also provided.
14
+
15
+ Operations:
16
+ - try_acquire(tokens=1): non-blocking, returns True/False
17
+ - acquire(tokens=1): blocking, sleeps until tokens available
18
+ - wait_time(tokens=1): returns seconds to wait
19
+
20
+ Properties:
21
+ - Allows bursts up to capacity
22
+ - Long-term rate is capped at refill_rate
23
+ - O(1) per operation
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import time
28
+ from collections import deque
29
+ from typing import Any
30
+
31
+
32
+ class TokenBucket:
33
+ """Token bucket rate limiter.
34
+
35
+ Args:
36
+ capacity: Maximum tokens the bucket can hold (burst size).
37
+ refill_rate: Tokens added per second (sustained rate).
38
+ """
39
+
40
+ def __init__(self, capacity: float, refill_rate: float) -> None:
41
+ if capacity <= 0:
42
+ raise ValueError("capacity must be positive")
43
+ if refill_rate <= 0:
44
+ raise ValueError("refill_rate must be positive")
45
+ self.capacity = float(capacity)
46
+ self.refill_rate = float(refill_rate)
47
+ self._tokens = float(capacity) # start full
48
+ self._last_refill = time.monotonic()
49
+
50
+ def _refill(self) -> None:
51
+ """Add tokens based on elapsed time since last refill."""
52
+ now = time.monotonic()
53
+ elapsed = now - self._last_refill
54
+ self._tokens = min(self.capacity, self._tokens + elapsed * self.refill_rate)
55
+ self._last_refill = now
56
+
57
+ def try_acquire(self, tokens: float = 1.0) -> bool:
58
+ """Non-blocking attempt to acquire tokens.
59
+
60
+ Returns True if tokens were acquired, False if insufficient.
61
+ """
62
+ self._refill()
63
+ if self._tokens >= tokens:
64
+ self._tokens -= tokens
65
+ return True
66
+ return False
67
+
68
+ def wait_time(self, tokens: float = 1.0) -> float:
69
+ """Return seconds to wait until tokens are available."""
70
+ self._refill()
71
+ if self._tokens >= tokens:
72
+ return 0.0
73
+ needed = tokens - self._tokens
74
+ return needed / self.refill_rate
75
+
76
+ def acquire(self, tokens: float = 1.0, timeout: float | None = None) -> bool:
77
+ """Blocking acquire — sleeps until tokens are available.
78
+
79
+ Args:
80
+ tokens: Number of tokens to acquire.
81
+ timeout: Maximum seconds to wait. None = wait forever.
82
+
83
+ Returns:
84
+ True if acquired, False if timed out.
85
+ """
86
+ deadline = None if timeout is None else time.monotonic() + timeout
87
+ while True:
88
+ wait = self.wait_time(tokens)
89
+ if wait == 0.0:
90
+ self._refill()
91
+ self._tokens -= tokens
92
+ return True
93
+ if deadline is not None and time.monotonic() + wait > deadline:
94
+ return False
95
+ time.sleep(wait)
96
+
97
+ @property
98
+ def available_tokens(self) -> float:
99
+ """Current token count (after refilling)."""
100
+ self._refill()
101
+ return self._tokens
102
+
103
+ def stats(self) -> dict[str, Any]:
104
+ return {
105
+ "capacity": self.capacity,
106
+ "refill_rate": self.refill_rate,
107
+ "available_tokens": round(self.available_tokens, 2),
108
+ }
109
+
110
+
111
+ class SlidingWindowCounter:
112
+ """Sliding window rate limiter — counts requests in a time window.
113
+
114
+ Uses a deque of timestamps, pruning entries older than the window.
115
+ Simple and accurate for small windows.
116
+
117
+ Args:
118
+ window_seconds: Size of the sliding window.
119
+ max_requests: Maximum requests allowed in the window.
120
+ """
121
+
122
+ def __init__(self, window_seconds: float, max_requests: int) -> None:
123
+ self.window_seconds = window_seconds
124
+ self.max_requests = max_requests
125
+ self._timestamps: deque[float] = deque()
126
+
127
+ def _prune(self, now: float) -> None:
128
+ """Remove timestamps outside the window."""
129
+ cutoff = now - self.window_seconds
130
+ while self._timestamps and self._timestamps[0] <= cutoff:
131
+ self._timestamps.popleft()
132
+
133
+ def try_acquire(self) -> bool:
134
+ """Non-blocking attempt to make a request."""
135
+ now = time.monotonic()
136
+ self._prune(now)
137
+ if len(self._timestamps) < self.max_requests:
138
+ self._timestamps.append(now)
139
+ return True
140
+ return False
141
+
142
+ def current_count(self) -> int:
143
+ """Number of requests in the current window."""
144
+ self._prune(time.monotonic())
145
+ return len(self._timestamps)
146
+
147
+ def stats(self) -> dict[str, Any]:
148
+ return {
149
+ "window_seconds": self.window_seconds,
150
+ "max_requests": self.max_requests,
151
+ "current_count": self.current_count(),
152
+ "remaining": max(0, self.max_requests - self.current_count()),
153
+ }
@@ -0,0 +1,94 @@
1
+ """B83. Refactor Transaction with Savepoint/Rollback.
2
+
3
+ Atomic refactoring with savepoints and rollback.
4
+ Source: CCASP pattern.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum, auto
10
+ from pathlib import Path
11
+
12
+
13
+ class TransactionStatus(Enum):
14
+ ACTIVE = auto()
15
+ COMMITTED = auto()
16
+ ROLLED_BACK = auto()
17
+
18
+
19
+ @dataclass
20
+ class Savepoint:
21
+ name: str
22
+ file_snapshots: dict[str, str] = field(default_factory=dict) # path → content
23
+
24
+
25
+ @dataclass
26
+ class TransactionResult:
27
+ status: TransactionStatus
28
+ savepoints: list[str] = field(default_factory=list)
29
+ files_modified: list[str] = field(default_factory=list)
30
+ error: str = ""
31
+
32
+
33
+ class RefactorTransaction:
34
+ """Atomic refactoring with savepoint/rollback for multi-file changes."""
35
+
36
+ def __init__(self, workspace: Path) -> None:
37
+ self._workspace = workspace
38
+ self._savepoints: list[Savepoint] = []
39
+ self._modified: set[str] = set()
40
+ self._status: TransactionStatus = TransactionStatus.ACTIVE
41
+
42
+ def savepoint(self, name: str, files: list[str]) -> Savepoint:
43
+ """Create a savepoint with snapshots of the given files."""
44
+ if self._status != TransactionStatus.ACTIVE:
45
+ raise RuntimeError("Transaction not active")
46
+
47
+ sp = Savepoint(name=name)
48
+ for filepath in files:
49
+ try:
50
+ content = Path(filepath).read_text(encoding="utf-8")
51
+ sp.file_snapshots[filepath] = content
52
+ except FileNotFoundError:
53
+ sp.file_snapshots[filepath] = ""
54
+ self._savepoints.append(sp)
55
+ return sp
56
+
57
+ def rollback_to(self, savepoint_name: str) -> None:
58
+ """Rollback to a named savepoint."""
59
+ sp = next((s for s in self._savepoints if s.name == savepoint_name), None)
60
+ if not sp:
61
+ raise KeyError(f"Savepoint '{savepoint_name}' not found")
62
+
63
+ for filepath, content in sp.file_snapshots.items():
64
+ Path(filepath).write_text(content, encoding="utf-8")
65
+
66
+ # Remove savepoints after this one
67
+ idx = self._savepoints.index(sp)
68
+ self._savepoints = self._savepoints[:idx + 1]
69
+
70
+ def rollback_all(self) -> None:
71
+ """Rollback to the first savepoint (undo everything)."""
72
+ if self._savepoints:
73
+ self.rollback_to(self._savepoints[0].name)
74
+ self._status = TransactionStatus.ROLLED_BACK
75
+
76
+ def commit(self) -> TransactionResult:
77
+ """Commit the transaction — changes are permanent."""
78
+ self._status = TransactionStatus.COMMITTED
79
+ return TransactionResult(
80
+ status=self._status,
81
+ savepoints=[sp.name for sp in self._savepoints],
82
+ files_modified=list(self._modified),
83
+ )
84
+
85
+ def record_modification(self, filepath: str) -> None:
86
+ self._modified.add(filepath)
87
+
88
+ @property
89
+ def status(self) -> TransactionStatus:
90
+ return self._status
91
+
92
+ @property
93
+ def savepoint_names(self) -> list[str]:
94
+ return [sp.name for sp in self._savepoints]
@@ -0,0 +1,108 @@
1
+ """B68. Virtual File System for Research Artifacts.
2
+
3
+ Branch, persist, rejoin research investigations.
4
+ Source: Morgana pattern.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ @dataclass
13
+ class ResearchArtifact:
14
+ id: str
15
+ name: str
16
+ content: str
17
+ artifact_type: str = "text" # text, outline, finding, source, draft
18
+ created_at: float = field(default_factory=time.time)
19
+ tags: list[str] = field(default_factory=list)
20
+
21
+
22
+ @dataclass
23
+ class ResearchBranch:
24
+ id: str
25
+ parent_id: str | None = None
26
+ name: str = ""
27
+ artifacts: dict[str, ResearchArtifact] = field(default_factory=dict)
28
+ created_at: float = field(default_factory=time.time)
29
+ status: str = "active" # active, merged, abandoned
30
+
31
+
32
+ class ResearchWorkspace:
33
+ """Virtual file system for research artifacts with branching."""
34
+
35
+ def __init__(self) -> None:
36
+ self._branches: dict[str, ResearchBranch] = {}
37
+ self._counter = 0
38
+ # Create root branch
39
+ self.create_branch("main", name="Main Research")
40
+
41
+ def create_branch(self, branch_id: str, parent_id: str | None = None,
42
+ name: str = "") -> ResearchBranch:
43
+ branch = ResearchBranch(id=branch_id, parent_id=parent_id, name=name)
44
+ self._branches[branch_id] = branch
45
+ return branch
46
+
47
+ def add_artifact(self, branch_id: str, artifact: ResearchArtifact) -> None:
48
+ branch = self._branches.get(branch_id)
49
+ if not branch:
50
+ raise KeyError(f"Branch {branch_id} not found")
51
+ branch.artifacts[artifact.id] = artifact
52
+
53
+ def get_artifact(self, branch_id: str, artifact_id: str) -> ResearchArtifact | None:
54
+ branch = self._branches.get(branch_id)
55
+ if not branch:
56
+ return None
57
+ if artifact_id in branch.artifacts:
58
+ return branch.artifacts[artifact_id]
59
+ # Walk parent chain
60
+ if branch.parent_id:
61
+ return self.get_artifact(branch.parent_id, artifact_id)
62
+ return None
63
+
64
+ def list_artifacts(self, branch_id: str, include_parent: bool = True) -> list[ResearchArtifact]:
65
+ branch = self._branches.get(branch_id)
66
+ if not branch:
67
+ return []
68
+ artifacts = list(branch.artifacts.values())
69
+ if include_parent and branch.parent_id:
70
+ artifacts.extend(self.list_artifacts(branch.parent_id, include_parent=True))
71
+ return artifacts
72
+
73
+ def merge(self, source_id: str, target_id: str) -> int:
74
+ """Merge artifacts from source branch into target. Returns count merged."""
75
+ source = self._branches.get(source_id)
76
+ target = self._branches.get(target_id)
77
+ if not source or not target:
78
+ return 0
79
+ count = 0
80
+ for aid, artifact in source.artifacts.items():
81
+ if aid not in target.artifacts:
82
+ target.artifacts[aid] = artifact
83
+ count += 1
84
+ source.status = "merged"
85
+ return count
86
+
87
+ def search(self, branch_id: str, query: str) -> list[ResearchArtifact]:
88
+ """Search artifacts by name, content, or tags."""
89
+ artifacts = self.list_artifacts(branch_id)
90
+ query_lower = query.lower()
91
+ return [
92
+ a for a in artifacts
93
+ if query_lower in a.name.lower()
94
+ or query_lower in a.content.lower()
95
+ or any(query_lower in t.lower() for t in a.tags)
96
+ ]
97
+
98
+ def new_artifact_id(self) -> str:
99
+ self._counter += 1
100
+ return f"art_{self._counter}"
101
+
102
+ @property
103
+ def branch_count(self) -> int:
104
+ return len(self._branches)
105
+
106
+ @property
107
+ def artifact_count(self) -> int:
108
+ return sum(len(b.artifacts) for b in self._branches.values())
@@ -0,0 +1,72 @@
1
+ """H3 — Retraction Ledger.
2
+
3
+ Append-only retraction records, never silent deletes.
4
+ Mined from T3MP3ST INTEGRITY_LEDGER.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from typing import Any
11
+
12
+
13
+ @dataclass
14
+ class RetractionEntry:
15
+ """A single retraction record."""
16
+
17
+ id: str
18
+ target_id: str
19
+ reason: str
20
+ retracted_at: float = field(default_factory=time.time)
21
+ retracted_by: str = ""
22
+ metadata: dict[str, Any] = field(default_factory=dict)
23
+
24
+ def to_dict(self) -> dict[str, Any]:
25
+ return {
26
+ "id": self.id,
27
+ "target_id": self.target_id,
28
+ "reason": self.reason,
29
+ "retracted_at": self.retracted_at,
30
+ "retracted_by": self.retracted_by,
31
+ "metadata": dict(self.metadata),
32
+ }
33
+
34
+
35
+ class RetractionLedger:
36
+ """Append-only ledger — entries can never be deleted, only added."""
37
+
38
+ def __init__(self) -> None:
39
+ self._entries: list[RetractionEntry] = []
40
+
41
+ def add(
42
+ self,
43
+ target_id: str,
44
+ reason: str,
45
+ retracted_by: str = "",
46
+ metadata: dict[str, Any] | None = None,
47
+ ) -> RetractionEntry:
48
+ entry = RetractionEntry(
49
+ id=f"retraction-{len(self._entries) + 1:06d}",
50
+ target_id=target_id,
51
+ reason=reason,
52
+ retracted_by=retracted_by,
53
+ metadata=metadata or {},
54
+ )
55
+ self._entries.append(entry)
56
+ return entry
57
+
58
+ def is_retracted(self, target_id: str) -> bool:
59
+ return any(e.target_id == target_id for e in self._entries)
60
+
61
+ def get_retractions(self, target_id: str) -> list[RetractionEntry]:
62
+ return [e for e in self._entries if e.target_id == target_id]
63
+
64
+ def all(self) -> list[RetractionEntry]:
65
+ return list(self._entries)
66
+
67
+ def count(self) -> int:
68
+ return len(self._entries)
69
+
70
+ def delete(self, id: str) -> bool:
71
+ """Always returns False — retractions are permanent."""
72
+ return False
@@ -0,0 +1,88 @@
1
+ """B74. Saga Pattern for Multi-Agent Compensation.
2
+
3
+ Each step defines a compensating action. On failure, execute
4
+ compensations in reverse order.
5
+ Source: Claudient pattern.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum, auto
11
+ from typing import Callable, Any
12
+
13
+
14
+ class SagaStepStatus(Enum):
15
+ PENDING = auto()
16
+ RUNNING = auto()
17
+ DONE = auto()
18
+ FAILED = auto()
19
+ COMPENSATED = auto()
20
+
21
+
22
+ @dataclass
23
+ class SagaStep:
24
+ name: str
25
+ execute: Callable[[], Any]
26
+ compensate: Callable[[], Any] | None = None
27
+ status: SagaStepStatus = SagaStepStatus.PENDING
28
+ result: Any = None
29
+ error: str | None = None
30
+
31
+
32
+ @dataclass
33
+ class SagaResult:
34
+ success: bool
35
+ completed_steps: list[str] = field(default_factory=list)
36
+ compensated_steps: list[str] = field(default_factory=list)
37
+ failed_step: str | None = None
38
+ error: str | None = None
39
+
40
+
41
+ class SagaOrchestrator:
42
+ """Execute multi-step operations with automatic rollback."""
43
+
44
+ def __init__(self) -> None:
45
+ self._steps: list[SagaStep] = []
46
+
47
+ def add_step(self, step: SagaStep) -> None:
48
+ self._steps.append(step)
49
+
50
+ def execute(self) -> SagaResult:
51
+ """Execute all steps. On failure, compensate in reverse."""
52
+ result = SagaResult(success=True)
53
+
54
+ for step in self._steps:
55
+ step.status = SagaStepStatus.RUNNING
56
+ try:
57
+ step.result = step.execute()
58
+ step.status = SagaStepStatus.DONE
59
+ result.completed_steps.append(step.name)
60
+ except Exception as e:
61
+ step.error = str(e)
62
+ step.status = SagaStepStatus.FAILED
63
+ result.success = False
64
+ result.failed_step = step.name
65
+ result.error = str(e)
66
+
67
+ # Compensate in reverse
68
+ self._compensate(result)
69
+ break
70
+
71
+ return result
72
+
73
+ def _compensate(self, result: SagaResult) -> None:
74
+ """Run compensations in reverse order."""
75
+ for step in reversed(self._steps):
76
+ if step.status != SagaStepStatus.DONE:
77
+ continue
78
+ if step.compensate:
79
+ try:
80
+ step.compensate()
81
+ step.status = SagaStepStatus.COMPENSATED
82
+ result.compensated_steps.append(step.name)
83
+ except Exception:
84
+ pass # compensation failed — log but continue
85
+
86
+ @property
87
+ def steps(self) -> list[SagaStep]:
88
+ return list(self._steps)
@@ -0,0 +1,100 @@
1
+ """B52. Session Forking + Compaction Circuit Breaker.
2
+
3
+ Fork sessions with parent pointers (no message duplication).
4
+ Compaction circuit breaker prevents infinite compaction loops.
5
+ Source: aloop pattern.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from dataclasses import dataclass, field
11
+
12
+
13
+ @dataclass
14
+ class Message:
15
+ role: str # "user", "assistant", "tool"
16
+ content: str
17
+ tool_call_id: str | None = None
18
+ timestamp: float = field(default_factory=time.time)
19
+
20
+
21
+ @dataclass
22
+ class SessionFork:
23
+ id: str
24
+ parent_id: str | None = None
25
+ messages: list[Message] = field(default_factory=list)
26
+ fork_point: int = 0 # index in parent's messages where fork occurred
27
+ depth: int = 0
28
+ created_at: float = field(default_factory=time.time)
29
+ materialized: bool = False
30
+
31
+ MAX_FORK_DEPTH = 10
32
+
33
+
34
+ class SessionStore:
35
+ """Manage sessions with fork support and compaction circuit breaker."""
36
+
37
+ def __init__(self, max_compaction_failures: int = 3) -> None:
38
+ self._sessions: dict[str, SessionFork] = {}
39
+ self._compaction_failures: dict[str, int] = {}
40
+ self._max_compaction_failures = max_compaction_failures
41
+
42
+ def create(self, session_id: str, messages: list[Message] | None = None) -> SessionFork:
43
+ s = SessionFork(id=session_id, messages=list(messages or []))
44
+ self._sessions[session_id] = s
45
+ return s
46
+
47
+ def fork(
48
+ self,
49
+ parent_id: str,
50
+ fork_id: str,
51
+ fork_point: int | None = None,
52
+ ) -> SessionFork:
53
+ parent = self._sessions.get(parent_id)
54
+ if parent is None:
55
+ raise KeyError(f"Parent session {parent_id} not found")
56
+ if parent.depth >= SessionFork.MAX_FORK_DEPTH:
57
+ raise ValueError(f"Max fork depth ({SessionFork.MAX_FORK_DEPTH}) exceeded")
58
+
59
+ fp = fork_point if fork_point is not None else len(parent.messages)
60
+ child = SessionFork(
61
+ id=fork_id,
62
+ parent_id=parent_id,
63
+ fork_point=fp,
64
+ depth=parent.depth + 1,
65
+ )
66
+ # Child does NOT copy messages — uses parent pointer
67
+ self._sessions[fork_id] = child
68
+ return child
69
+
70
+ def get_messages(self, session_id: str) -> list[Message]:
71
+ """Resolve messages by walking parent chain."""
72
+ s = self._sessions.get(session_id)
73
+ if s is None:
74
+ return []
75
+ if s.parent_id is None:
76
+ return list(s.messages)
77
+ parent_msgs = self.get_messages(s.parent_id)[: s.fork_point]
78
+ return parent_msgs + list(s.messages)
79
+
80
+ def materialize(self, session_id: str) -> list[Message]:
81
+ """Flatten fork chain into a single message list."""
82
+ msgs = self.get_messages(session_id)
83
+ s = self._sessions[session_id]
84
+ s.materialized = True
85
+ s.messages = list(msgs)
86
+ s.parent_id = None
87
+ return msgs
88
+
89
+ def can_compact(self, session_id: str) -> bool:
90
+ """Circuit breaker: prevent compaction if too many failures."""
91
+ return self._compaction_failures.get(session_id, 0) < self._max_compaction_failures
92
+
93
+ def record_compaction_failure(self, session_id: str) -> None:
94
+ self._compaction_failures[session_id] = self._compaction_failures.get(session_id, 0) + 1
95
+
96
+ def reset_compaction(self, session_id: str) -> None:
97
+ self._compaction_failures.pop(session_id, None)
98
+
99
+ def list_forks(self, parent_id: str) -> list[str]:
100
+ return [sid for sid, s in self._sessions.items() if s.parent_id == parent_id]
@@ -0,0 +1,67 @@
1
+ """B79. Shadow Editor: LSP Diff Validation.
2
+
3
+ Diff LSP diagnostics before and after each edit to catch introduced errors.
4
+ Source: Pathfinder pattern.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Callable
10
+
11
+
12
+ @dataclass
13
+ class LSPDiagnostic:
14
+ severity: str
15
+ message: str
16
+ line: int
17
+ col: int
18
+ source: str = ""
19
+
20
+
21
+ @dataclass
22
+ class EditValidation:
23
+ safe: bool
24
+ introduced: list[LSPDiagnostic] = field(default_factory=list)
25
+ resolved: list[LSPDiagnostic] = field(default_factory=list)
26
+ unchanged: list[LSPDiagnostic] = field(default_factory=list)
27
+
28
+
29
+ class ShadowEditor:
30
+ """Validate edits against LSP before writing to disk."""
31
+
32
+ def __init__(self, get_diagnostics_fn: Callable[[str, str], list[LSPDiagnostic]] | None = None) -> None:
33
+ self._get_diagnostics = get_diagnostics_fn or self._noop_diagnostics
34
+
35
+ @staticmethod
36
+ def _noop_diagnostics(filepath: str, content: str) -> list[LSPDiagnostic]:
37
+ return []
38
+
39
+ def validate_edit(self, filepath: str, old_content: str, new_content: str) -> EditValidation:
40
+ """Validate an edit by comparing diagnostics before and after."""
41
+ before = self._get_diagnostics(filepath, old_content)
42
+ after = self._get_diagnostics(filepath, new_content)
43
+
44
+ introduced = self._diff_new(before, after)
45
+ resolved = self._diff_new(after, before)
46
+ unchanged = self._intersect(before, after)
47
+
48
+ return EditValidation(
49
+ safe=len([d for d in introduced if d.severity == "error"]) == 0,
50
+ introduced=introduced,
51
+ resolved=resolved,
52
+ unchanged=unchanged,
53
+ )
54
+
55
+ def _diff_new(self, before: list[LSPDiagnostic], after: list[LSPDiagnostic]) -> list[LSPDiagnostic]:
56
+ """Find diagnostics in 'after' that weren't in 'before'."""
57
+ before_keys = {(d.severity, d.message, d.line) for d in before}
58
+ return [d for d in after if (d.severity, d.message, d.line) not in before_keys]
59
+
60
+ def _intersect(self, before: list[LSPDiagnostic], after: list[LSPDiagnostic]) -> list[LSPDiagnostic]:
61
+ """Find diagnostics present in both."""
62
+ after_keys = {(d.severity, d.message, d.line) for d in after}
63
+ return [d for d in before if (d.severity, d.message, d.line) in after_keys]
64
+
65
+ def should_block(self, validation: EditValidation) -> bool:
66
+ """Should the edit be blocked?"""
67
+ return any(d.severity == "error" for d in validation.introduced)