ct-toolkit 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/PKG-INFO +11 -6
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/README.md +10 -5
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/wrapper.py +66 -29
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/engine.py +18 -3
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/endorsement/reflective.py +1 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/provenance/log.py +100 -16
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit.egg-info/PKG-INFO +11 -6
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/pyproject.toml +1 -1
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/LICENSE +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit.egg-info/SOURCES.txt +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/setup.cfg +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.0 → ct_toolkit-0.3.2}/tests/test_summarization_drift.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -51,6 +51,7 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
## 📖 Official Documentation
|
|
54
|
+
|
|
54
55
|
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
55
56
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
56
57
|
|
|
@@ -63,6 +64,7 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
63
64
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
64
65
|
|
|
65
66
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
67
|
+
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
66
68
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
67
69
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
68
70
|
- **Provenance Log**: Immutable HMAC-signed interaction history.
|
|
@@ -92,15 +94,16 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
92
94
|
|
|
93
95
|
## 🚦 Project Health & Status
|
|
94
96
|
|
|
95
|
-
| Metric
|
|
96
|
-
|
|
|
97
|
-
| **Tests**
|
|
98
|
-
| **Last Phase**
|
|
99
|
-
| **Current Goal** | 🔶 Phase 5: Vault
|
|
97
|
+
| Metric | Status |
|
|
98
|
+
| :--------------- | :------------------------------------------------------ |
|
|
99
|
+
| **Tests** | ✅ 231/232 passing (90% coverage) |
|
|
100
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
101
|
+
| **Current Goal** | 🔶 Phase 5: Vault & Security (In Progress) |
|
|
100
102
|
|
|
101
103
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
102
104
|
|
|
103
105
|
### Framework & Model Support
|
|
106
|
+
|
|
104
107
|
Seamlessly integrate with your favorite frameworks and local models:
|
|
105
108
|
|
|
106
109
|
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
@@ -111,9 +114,11 @@ Seamlessly integrate with your favorite frameworks and local models:
|
|
|
111
114
|
---
|
|
112
115
|
|
|
113
116
|
## Theoretical Foundation
|
|
117
|
+
|
|
114
118
|
Translating the framework proposed in [**The Computational Theseus (2025)**](https://hakandamar.com/the-computational-theseus-engineering-identity-continuity-as-a-guardrail-against-sequential-963918c1720d) into engineering practice.
|
|
115
119
|
|
|
116
120
|
---
|
|
117
121
|
|
|
118
122
|
## License
|
|
123
|
+
|
|
119
124
|
Apache License 2.0.
|
|
@@ -12,6 +12,7 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## 📖 Official Documentation
|
|
15
|
+
|
|
15
16
|
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
16
17
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
17
18
|
|
|
@@ -24,6 +25,7 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
24
25
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
25
26
|
|
|
26
27
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
28
|
+
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
27
29
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
28
30
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
29
31
|
- **Provenance Log**: Immutable HMAC-signed interaction history.
|
|
@@ -53,15 +55,16 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
53
55
|
|
|
54
56
|
## 🚦 Project Health & Status
|
|
55
57
|
|
|
56
|
-
| Metric
|
|
57
|
-
|
|
|
58
|
-
| **Tests**
|
|
59
|
-
| **Last Phase**
|
|
60
|
-
| **Current Goal** | 🔶 Phase 5: Vault
|
|
58
|
+
| Metric | Status |
|
|
59
|
+
| :--------------- | :------------------------------------------------------ |
|
|
60
|
+
| **Tests** | ✅ 231/232 passing (90% coverage) |
|
|
61
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
62
|
+
| **Current Goal** | 🔶 Phase 5: Vault & Security (In Progress) |
|
|
61
63
|
|
|
62
64
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
63
65
|
|
|
64
66
|
### Framework & Model Support
|
|
67
|
+
|
|
65
68
|
Seamlessly integrate with your favorite frameworks and local models:
|
|
66
69
|
|
|
67
70
|
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
@@ -72,9 +75,11 @@ Seamlessly integrate with your favorite frameworks and local models:
|
|
|
72
75
|
---
|
|
73
76
|
|
|
74
77
|
## Theoretical Foundation
|
|
78
|
+
|
|
75
79
|
Translating the framework proposed in [**The Computational Theseus (2025)**](https://hakandamar.com/the-computational-theseus-engineering-identity-continuity-as-a-guardrail-against-sequential-963918c1720d) into engineering practice.
|
|
76
80
|
|
|
77
81
|
---
|
|
78
82
|
|
|
79
83
|
## License
|
|
84
|
+
|
|
80
85
|
Apache License 2.0.
|
|
@@ -59,6 +59,10 @@ class WrapperConfig:
|
|
|
59
59
|
embedding_model: str = "text-embedding-3-small"
|
|
60
60
|
enterprise_mode: bool = False # Run all tiers all the time
|
|
61
61
|
parent_kernel: ConstitutionalKernel | None = None # Propagated from mother agent
|
|
62
|
+
|
|
63
|
+
# -- Auto-Correction Loop --
|
|
64
|
+
auto_correction: bool = False
|
|
65
|
+
max_correction_retries: int = 1
|
|
62
66
|
|
|
63
67
|
# -- Dynamic Stability-Plasticity Scheduling --
|
|
64
68
|
elasticity_max_thresholds: tuple[float, float, float] | None = None # (max_l1, max_l2, max_l3)
|
|
@@ -385,34 +389,52 @@ class TheseusWrapper:
|
|
|
385
389
|
f"Please provide a client or set the appropriate environment variables."
|
|
386
390
|
)
|
|
387
391
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
392
|
+
retries = 0
|
|
393
|
+
max_retries = self._config.max_correction_retries if self._config.auto_correction else 0
|
|
394
|
+
|
|
395
|
+
while retries <= max_retries:
|
|
396
|
+
start_time = time.monotonic()
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
raw_response = self._call_provider(messages, model=model, **kwargs)
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logger.error(f"Provider API error: {e}")
|
|
402
|
+
raise
|
|
403
|
+
|
|
404
|
+
elapsed = time.monotonic() - start_time
|
|
405
|
+
content = self._extract_content(raw_response)
|
|
406
|
+
model_used = self._extract_model(raw_response, model)
|
|
407
|
+
self._last_model = model_used
|
|
408
|
+
|
|
409
|
+
# Compute current interaction experience
|
|
410
|
+
interaction_count = 0
|
|
411
|
+
if self._config.log_requests:
|
|
412
|
+
interaction_count = self._provenance_log.get_interaction_count(
|
|
413
|
+
template=self._config.template,
|
|
414
|
+
kernel_name=self._kernel.name,
|
|
415
|
+
model=model_used
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Divergence Engine (L1 -> L2 -> L3)
|
|
419
|
+
skip_l3 = self._config.auto_correction and retries < max_retries
|
|
420
|
+
div_result = self._run_divergence_engine(
|
|
421
|
+
message=message,
|
|
422
|
+
response=content,
|
|
423
|
+
interaction_count=interaction_count,
|
|
424
|
+
skip_l3=skip_l3,
|
|
408
425
|
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
426
|
+
|
|
427
|
+
from ct_toolkit.divergence.l2_judge import JudgeVerdict
|
|
428
|
+
if getattr(div_result, 'l2_verdict', None) == JudgeVerdict.MISALIGNED and retries < max_retries:
|
|
429
|
+
logger.warning(f"Auto-correction triggered. Reason: {div_result.l2_reason}")
|
|
430
|
+
messages.extend([
|
|
431
|
+
{"role": "assistant", "content": content},
|
|
432
|
+
{"role": "user", "content": f"Identity Drift Detected. Your previous response violated the core identity instructions. Reason: {div_result.l2_reason}. Please revise your response to strictly adhere to the system prompt."}
|
|
433
|
+
])
|
|
434
|
+
retries += 1
|
|
435
|
+
continue
|
|
436
|
+
|
|
437
|
+
break
|
|
416
438
|
|
|
417
439
|
# Provenance Log record
|
|
418
440
|
provenance_id = None
|
|
@@ -582,10 +604,10 @@ class TheseusWrapper:
|
|
|
582
604
|
return raw.model
|
|
583
605
|
return fallback or "unknown"
|
|
584
606
|
|
|
585
|
-
def _run_divergence_engine(self, message: str, response: str, interaction_count: int = 0) -> Any:
|
|
607
|
+
def _run_divergence_engine(self, message: str, response: str, interaction_count: int = 0, skip_l3: bool = False) -> Any:
|
|
586
608
|
"""Runs the staged divergence engine (L1 -> L2 -> L3)."""
|
|
587
609
|
try:
|
|
588
|
-
return self._divergence_engine.analyze(message, response, interaction_count)
|
|
610
|
+
return self._divergence_engine.analyze(message, response, interaction_count, skip_l3=skip_l3)
|
|
589
611
|
except Exception as e:
|
|
590
612
|
logger.error(f"Divergence Engine execution failed: {e}")
|
|
591
613
|
from ct_toolkit.divergence.engine import DivergenceResult, DivergenceTier
|
|
@@ -635,6 +657,21 @@ class TheseusWrapper:
|
|
|
635
657
|
commitment_new_value=commitment_new_value,
|
|
636
658
|
)
|
|
637
659
|
|
|
660
|
+
def export_provenance_log(self) -> list[dict[str, Any]]:
|
|
661
|
+
"""
|
|
662
|
+
Exports the entire verified provenance log.
|
|
663
|
+
|
|
664
|
+
Verifies the integrity of the log before returning the data.
|
|
665
|
+
This is intended for external auditing and analysis.
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
A list of all log entries as dictionaries.
|
|
669
|
+
|
|
670
|
+
Raises:
|
|
671
|
+
ChainIntegrityError: If the log's hash chain is broken.
|
|
672
|
+
"""
|
|
673
|
+
return self._provenance_log.export_log()
|
|
674
|
+
|
|
638
675
|
@property
|
|
639
676
|
def kernel(self) -> ConstitutionalKernel:
|
|
640
677
|
return self._kernel
|
|
@@ -154,6 +154,7 @@ class DivergenceEngine:
|
|
|
154
154
|
request_text: str,
|
|
155
155
|
response_text: str,
|
|
156
156
|
interaction_count: int = 0,
|
|
157
|
+
skip_l3: bool = False,
|
|
157
158
|
) -> DivergenceResult:
|
|
158
159
|
"""
|
|
159
160
|
Performs full divergence analysis.
|
|
@@ -166,8 +167,8 @@ class DivergenceEngine:
|
|
|
166
167
|
)
|
|
167
168
|
|
|
168
169
|
if self._enterprise:
|
|
169
|
-
return self._enterprise_analyze(request_text, response_text)
|
|
170
|
-
return self._standard_analyze(request_text, response_text)
|
|
170
|
+
return self._enterprise_analyze(request_text, response_text, skip_l3=skip_l3)
|
|
171
|
+
return self._standard_analyze(request_text, response_text, skip_l3=skip_l3)
|
|
171
172
|
|
|
172
173
|
def get_drift_report(self, window_size: int = 50, model: str | None = None) -> DriftReport | None:
|
|
173
174
|
"""
|
|
@@ -189,6 +190,7 @@ class DivergenceEngine:
|
|
|
189
190
|
self,
|
|
190
191
|
request_text: str,
|
|
191
192
|
response_text: str,
|
|
193
|
+
skip_l3: bool = False,
|
|
192
194
|
) -> DivergenceResult:
|
|
193
195
|
"""Progressive analysis: L1 → L2 if needed → L3 if needed."""
|
|
194
196
|
|
|
@@ -236,6 +238,18 @@ class DivergenceEngine:
|
|
|
236
238
|
)
|
|
237
239
|
|
|
238
240
|
# L3 — L2 problematic or L1 too high
|
|
241
|
+
if skip_l3:
|
|
242
|
+
return DivergenceResult(
|
|
243
|
+
tier=DivergenceTier.L2_JUDGE,
|
|
244
|
+
l1_score=l1_score,
|
|
245
|
+
l2_verdict=l2_result.verdict,
|
|
246
|
+
l2_confidence=l2_result.confidence,
|
|
247
|
+
l2_reason=l2_result.reason,
|
|
248
|
+
action_required=True,
|
|
249
|
+
cascade_blocked=True,
|
|
250
|
+
summary="L3 skipped due to auto-correction retry logic",
|
|
251
|
+
)
|
|
252
|
+
|
|
239
253
|
if not self._l3_available:
|
|
240
254
|
return DivergenceResult(
|
|
241
255
|
tier=DivergenceTier.L2_JUDGE,
|
|
@@ -269,6 +283,7 @@ class DivergenceEngine:
|
|
|
269
283
|
self,
|
|
270
284
|
request_text: str,
|
|
271
285
|
response_text: str,
|
|
286
|
+
skip_l3: bool = False,
|
|
272
287
|
) -> DivergenceResult:
|
|
273
288
|
"""Enterprise mode: L1 + L2 + L3 all run, decision based on total score."""
|
|
274
289
|
l1_score = self._run_l1(response_text)
|
|
@@ -278,7 +293,7 @@ class DivergenceEngine:
|
|
|
278
293
|
l2_result = self._run_l2(request_text, response_text)
|
|
279
294
|
|
|
280
295
|
l3_report = None
|
|
281
|
-
if self._l3_available:
|
|
296
|
+
if self._l3_available and not skip_l3:
|
|
282
297
|
l3_report = self._run_l3(max_probes=5) # Fast subset in Enterprise
|
|
283
298
|
|
|
284
299
|
# Calculate total risk score
|
|
@@ -210,6 +210,7 @@ class ReflectiveEndorsement:
|
|
|
210
210
|
self._log = provenance_log
|
|
211
211
|
self._approval_channel = approval_channel or cli_approval_channel
|
|
212
212
|
self._pending_records: list[EndorsementRecord] = []
|
|
213
|
+
self._pre_update_snapshot: dict[str, Any] | None = None
|
|
213
214
|
|
|
214
215
|
# ── Main Flow ───────────────────────────────────────────────────────────────
|
|
215
216
|
|
|
@@ -43,6 +43,7 @@ class ProvenanceEntry:
|
|
|
43
43
|
divergence_score: float | None
|
|
44
44
|
metadata: dict[str, Any]
|
|
45
45
|
prev_entry_hash: str # Content hash of the previous entry
|
|
46
|
+
status: str = field(default="active") # "active" or "rolled_back"
|
|
46
47
|
hmac_signature: str = field(default="", repr=False)
|
|
47
48
|
|
|
48
49
|
def content_hash(self) -> str:
|
|
@@ -56,6 +57,7 @@ class ProvenanceEntry:
|
|
|
56
57
|
"divergence_score": self.divergence_score,
|
|
57
58
|
"metadata": self.metadata,
|
|
58
59
|
"prev_entry_hash": self.prev_entry_hash,
|
|
60
|
+
"status": self.status,
|
|
59
61
|
},
|
|
60
62
|
sort_keys=True,
|
|
61
63
|
)
|
|
@@ -111,6 +113,7 @@ class ProvenanceLog:
|
|
|
111
113
|
divergence_score=divergence_score,
|
|
112
114
|
metadata=metadata or {},
|
|
113
115
|
prev_entry_hash=prev_hash,
|
|
116
|
+
status="active",
|
|
114
117
|
)
|
|
115
118
|
|
|
116
119
|
content_hash = entry.content_hash()
|
|
@@ -124,13 +127,14 @@ class ProvenanceLog:
|
|
|
124
127
|
|
|
125
128
|
def verify_chain(self) -> bool:
|
|
126
129
|
"""
|
|
127
|
-
Verifies the
|
|
130
|
+
Verifies the active chain from start to finish.
|
|
128
131
|
- Is each entry's HMAC valid?
|
|
129
132
|
- Does each entry correctly reference the previous one?
|
|
133
|
+
- Ignores 'rolled_back' entries.
|
|
130
134
|
|
|
131
135
|
Returns: True (valid) | raises ChainIntegrityError
|
|
132
136
|
"""
|
|
133
|
-
entries = self._load_all_entries()
|
|
137
|
+
entries = self._load_all_entries(include_rolled_back=False)
|
|
134
138
|
if not entries:
|
|
135
139
|
return True
|
|
136
140
|
|
|
@@ -148,12 +152,12 @@ class ProvenanceLog:
|
|
|
148
152
|
|
|
149
153
|
expected_prev = entry.content_hash()
|
|
150
154
|
|
|
151
|
-
logger.info(f"Chain verified: {len(entries)} entries valid.")
|
|
155
|
+
logger.info(f"Chain verified: {len(entries)} active entries valid.")
|
|
152
156
|
return True
|
|
153
157
|
|
|
154
|
-
def get_entries(self, limit: int = 100) -> list[ProvenanceEntry]:
|
|
158
|
+
def get_entries(self, limit: int = 100, include_rolled_back: bool = False) -> list[ProvenanceEntry]:
|
|
155
159
|
"""Returns the last N entries."""
|
|
156
|
-
return self._load_all_entries(limit=limit)
|
|
160
|
+
return self._load_all_entries(limit=limit, include_rolled_back=include_rolled_back)
|
|
157
161
|
|
|
158
162
|
def get_entry(self, entry_id: str) -> ProvenanceEntry | None:
|
|
159
163
|
cursor = self._conn.execute(
|
|
@@ -166,15 +170,15 @@ class ProvenanceLog:
|
|
|
166
170
|
self, template: str, kernel_name: str, model: str
|
|
167
171
|
) -> int:
|
|
168
172
|
"""
|
|
169
|
-
Calculates the number of interactions matching the current context.
|
|
173
|
+
Calculates the number of active interactions matching the current context.
|
|
170
174
|
This provides the experience metric for Stability-Plasticity Scheduling.
|
|
171
175
|
"""
|
|
172
|
-
# We parse the JSON metadata column. SQLite JSON1 extension is standard.
|
|
173
176
|
query = """
|
|
174
177
|
SELECT COUNT(*) FROM provenance
|
|
175
178
|
WHERE json_extract(metadata, '$.template') = ?
|
|
176
179
|
AND json_extract(metadata, '$.kernel') = ?
|
|
177
180
|
AND json_extract(metadata, '$.model') = ?
|
|
181
|
+
AND status = 'active'
|
|
178
182
|
"""
|
|
179
183
|
try:
|
|
180
184
|
row = self._conn.execute(query, (template, kernel_name, model)).fetchone()
|
|
@@ -184,21 +188,81 @@ class ProvenanceLog:
|
|
|
184
188
|
f"Could not count interactions (JSON query failed): {e}. "
|
|
185
189
|
"Returning 0 to avoid inflating ElasticityScheduler thresholds."
|
|
186
190
|
)
|
|
187
|
-
# Return 0 (conservative) — do NOT return total count without filters
|
|
188
|
-
# as it would incorrectly inflate the interaction_count for the scheduler.
|
|
189
191
|
return 0
|
|
190
192
|
|
|
193
|
+
def export_log(self, include_rolled_back: bool = False) -> list[dict[str, Any]]:
|
|
194
|
+
"""
|
|
195
|
+
Exports the verified log as a list of dictionaries.
|
|
196
|
+
By default, only active entries are exported.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
include_rolled_back: If True, include all entries.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
A list of all log entries, where each entry is a dictionary.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ChainIntegrityError: If the active chain is invalid.
|
|
206
|
+
"""
|
|
207
|
+
self.verify_chain() # Ensure integrity before export
|
|
208
|
+
all_entries = self._load_all_entries(include_rolled_back=include_rolled_back)
|
|
209
|
+
return [asdict(entry) for entry in all_entries]
|
|
210
|
+
|
|
211
|
+
def get_read_only_connection(self) -> sqlite3.Connection:
|
|
212
|
+
"""
|
|
213
|
+
Returns a read-only connection to the database.
|
|
214
|
+
"""
|
|
215
|
+
try:
|
|
216
|
+
db_uri = self._vault_path.as_uri() + "?mode=ro"
|
|
217
|
+
return sqlite3.connect(db_uri, uri=True, check_same_thread=False)
|
|
218
|
+
except sqlite3.OperationalError as e:
|
|
219
|
+
raise VaultError(
|
|
220
|
+
"Could not open read-only connection. "
|
|
221
|
+
"Ensure the database file exists and has correct permissions."
|
|
222
|
+
) from e
|
|
223
|
+
|
|
224
|
+
def rollback(self, agent_id: str, entry_id: str) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Rolls back the log to the specified entry ID for a given agent.
|
|
227
|
+
All subsequent entries for that agent will be marked as 'rolled_back'.
|
|
228
|
+
"""
|
|
229
|
+
with self._lock:
|
|
230
|
+
entry = self.get_entry(entry_id)
|
|
231
|
+
if not entry:
|
|
232
|
+
raise ValueError(f"Entry with ID '{entry_id}' not found.")
|
|
233
|
+
|
|
234
|
+
# This is a simplification. In a real multi-agent system,
|
|
235
|
+
# we would need a more robust way to identify agent-specific entries.
|
|
236
|
+
# Here, we assume an 'agent_id' is stored in the metadata.
|
|
237
|
+
if entry.metadata.get("agent_id") != agent_id:
|
|
238
|
+
raise ValueError("Entry does not belong to the specified agent.")
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
self._conn.execute(
|
|
242
|
+
"""
|
|
243
|
+
UPDATE provenance
|
|
244
|
+
SET status = 'rolled_back'
|
|
245
|
+
WHERE timestamp > ? AND json_extract(metadata, '$.agent_id') = ?
|
|
246
|
+
""",
|
|
247
|
+
(entry.timestamp, agent_id),
|
|
248
|
+
)
|
|
249
|
+
self._conn.commit()
|
|
250
|
+
logger.info(f"Provenance log rolled back for agent '{agent_id}' to entry '{entry_id}'")
|
|
251
|
+
except sqlite3.Error as e:
|
|
252
|
+
raise VaultError(f"SQLite rollback error: {e}") from e
|
|
253
|
+
|
|
191
254
|
# ── Database ──────────────────────────────────────────────────────────────
|
|
192
255
|
|
|
193
256
|
def _init_db(self) -> sqlite3.Connection:
|
|
194
257
|
import os
|
|
195
258
|
self._vault_path.parent.mkdir(parents=True, exist_ok=True)
|
|
196
|
-
# Create file if not exist to set permissions before sqlite connection
|
|
197
259
|
if not self._vault_path.exists():
|
|
198
260
|
self._vault_path.touch()
|
|
199
261
|
self._vault_path.chmod(0o600)
|
|
200
262
|
|
|
201
263
|
conn = sqlite3.connect(str(self._vault_path), check_same_thread=False)
|
|
264
|
+
|
|
265
|
+
# Create table if it doesn't exist
|
|
202
266
|
conn.execute("""
|
|
203
267
|
CREATE TABLE IF NOT EXISTS provenance (
|
|
204
268
|
id TEXT PRIMARY KEY,
|
|
@@ -208,9 +272,18 @@ class ProvenanceLog:
|
|
|
208
272
|
divergence_score REAL,
|
|
209
273
|
metadata TEXT NOT NULL,
|
|
210
274
|
prev_entry_hash TEXT NOT NULL,
|
|
211
|
-
hmac_signature TEXT NOT NULL
|
|
275
|
+
hmac_signature TEXT NOT NULL,
|
|
276
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
212
277
|
)
|
|
213
278
|
""")
|
|
279
|
+
|
|
280
|
+
# Add status column for migration from older versions
|
|
281
|
+
try:
|
|
282
|
+
conn.execute("SELECT status FROM provenance LIMIT 1").fetchall()
|
|
283
|
+
except sqlite3.OperationalError:
|
|
284
|
+
conn.execute("ALTER TABLE provenance ADD COLUMN status TEXT DEFAULT 'active'")
|
|
285
|
+
logger.info("Migrated database: Added 'status' column to provenance table.")
|
|
286
|
+
|
|
214
287
|
conn.commit()
|
|
215
288
|
return conn
|
|
216
289
|
|
|
@@ -220,8 +293,8 @@ class ProvenanceLog:
|
|
|
220
293
|
"""
|
|
221
294
|
INSERT INTO provenance
|
|
222
295
|
(id, timestamp, request_hash, response_hash,
|
|
223
|
-
divergence_score, metadata, prev_entry_hash, hmac_signature)
|
|
224
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
296
|
+
divergence_score, metadata, prev_entry_hash, hmac_signature, status)
|
|
297
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
225
298
|
""",
|
|
226
299
|
(
|
|
227
300
|
entry.id,
|
|
@@ -232,22 +305,32 @@ class ProvenanceLog:
|
|
|
232
305
|
json.dumps(entry.metadata),
|
|
233
306
|
entry.prev_entry_hash,
|
|
234
307
|
entry.hmac_signature,
|
|
308
|
+
entry.status,
|
|
235
309
|
),
|
|
236
310
|
)
|
|
237
311
|
self._conn.commit()
|
|
238
312
|
except sqlite3.Error as e:
|
|
239
313
|
raise VaultError(f"SQLite write error: {e}") from e
|
|
240
314
|
|
|
241
|
-
def _load_all_entries(self, limit: int | None = None) -> list[ProvenanceEntry]:
|
|
242
|
-
query = "SELECT * FROM provenance
|
|
315
|
+
def _load_all_entries(self, limit: int | None = None, include_rolled_back: bool = False) -> list[ProvenanceEntry]:
|
|
316
|
+
query = "SELECT * FROM provenance"
|
|
317
|
+
conditions = []
|
|
318
|
+
if not include_rolled_back:
|
|
319
|
+
conditions.append("status = 'active'")
|
|
320
|
+
|
|
321
|
+
if conditions:
|
|
322
|
+
query += " WHERE " + " AND ".join(conditions)
|
|
323
|
+
|
|
324
|
+
query += " ORDER BY timestamp ASC"
|
|
243
325
|
if limit:
|
|
244
326
|
query += f" LIMIT {limit}"
|
|
327
|
+
|
|
245
328
|
rows = self._conn.execute(query).fetchall()
|
|
246
329
|
return [self._row_to_entry(r) for r in rows]
|
|
247
330
|
|
|
248
331
|
def _get_last_entry_hash(self) -> str:
|
|
249
332
|
row = self._conn.execute(
|
|
250
|
-
"SELECT * FROM provenance ORDER BY timestamp DESC LIMIT 1"
|
|
333
|
+
"SELECT * FROM provenance WHERE status = 'active' ORDER BY timestamp DESC LIMIT 1"
|
|
251
334
|
).fetchone()
|
|
252
335
|
if not row:
|
|
253
336
|
return self.GENESIS_HASH
|
|
@@ -264,6 +347,7 @@ class ProvenanceLog:
|
|
|
264
347
|
metadata=json.loads(row[5]),
|
|
265
348
|
prev_entry_hash=row[6],
|
|
266
349
|
hmac_signature=row[7],
|
|
350
|
+
status=row[8],
|
|
267
351
|
)
|
|
268
352
|
|
|
269
353
|
# ── Cryptography ────────────────────────────────────────────────────────────
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -51,6 +51,7 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
## 📖 Official Documentation
|
|
54
|
+
|
|
54
55
|
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
55
56
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
56
57
|
|
|
@@ -63,6 +64,7 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
63
64
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
64
65
|
|
|
65
66
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
67
|
+
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
66
68
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
67
69
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
68
70
|
- **Provenance Log**: Immutable HMAC-signed interaction history.
|
|
@@ -92,15 +94,16 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
92
94
|
|
|
93
95
|
## 🚦 Project Health & Status
|
|
94
96
|
|
|
95
|
-
| Metric
|
|
96
|
-
|
|
|
97
|
-
| **Tests**
|
|
98
|
-
| **Last Phase**
|
|
99
|
-
| **Current Goal** | 🔶 Phase 5: Vault
|
|
97
|
+
| Metric | Status |
|
|
98
|
+
| :--------------- | :------------------------------------------------------ |
|
|
99
|
+
| **Tests** | ✅ 231/232 passing (90% coverage) |
|
|
100
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
101
|
+
| **Current Goal** | 🔶 Phase 5: Vault & Security (In Progress) |
|
|
100
102
|
|
|
101
103
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
102
104
|
|
|
103
105
|
### Framework & Model Support
|
|
106
|
+
|
|
104
107
|
Seamlessly integrate with your favorite frameworks and local models:
|
|
105
108
|
|
|
106
109
|
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
@@ -111,9 +114,11 @@ Seamlessly integrate with your favorite frameworks and local models:
|
|
|
111
114
|
---
|
|
112
115
|
|
|
113
116
|
## Theoretical Foundation
|
|
117
|
+
|
|
114
118
|
Translating the framework proposed in [**The Computational Theseus (2025)**](https://hakandamar.com/the-computational-theseus-engineering-identity-continuity-as-a-guardrail-against-sequential-963918c1720d) into engineering practice.
|
|
115
119
|
|
|
116
120
|
---
|
|
117
121
|
|
|
118
122
|
## License
|
|
123
|
+
|
|
119
124
|
Apache License 2.0.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|