ct-toolkit 0.3.1__tar.gz → 0.3.3__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.1 → ct_toolkit-0.3.3}/PKG-INFO +11 -6
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/README.md +10 -5
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/__init__.py +0 -1
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/wrapper.py +56 -31
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/engine.py +18 -3
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/l3_icm.py +44 -33
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/endorsement/reflective.py +21 -18
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/embedding.py +4 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/provenance/log.py +11 -8
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit.egg-info/PKG-INFO +11 -6
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/pyproject.toml +1 -1
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/LICENSE +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit.egg-info/SOURCES.txt +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/setup.cfg +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.1 → ct_toolkit-0.3.3}/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.3
|
|
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.
|
|
@@ -24,7 +24,7 @@ import hashlib
|
|
|
24
24
|
import os
|
|
25
25
|
from dataclasses import dataclass, field
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Any, Iterator
|
|
27
|
+
from typing import Any, Iterator, Optional, Callable, Dict
|
|
28
28
|
import any_llm
|
|
29
29
|
from any_llm import AnyLLM
|
|
30
30
|
import glob
|
|
@@ -59,12 +59,18 @@ 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)
|
|
65
69
|
elasticity_growth_rate: float | None = None
|
|
66
70
|
risk_profile: RiskProfile | None = None
|
|
67
71
|
|
|
72
|
+
strict_embedding: bool = False # Raise error if embedding API fails
|
|
73
|
+
|
|
68
74
|
|
|
69
75
|
@dataclass
|
|
70
76
|
class CTResponse:
|
|
@@ -188,7 +194,7 @@ class TheseusWrapper:
|
|
|
188
194
|
if file_path.is_file():
|
|
189
195
|
self._integrity_monitor.register_file(file_path)
|
|
190
196
|
|
|
191
|
-
except
|
|
197
|
+
except Exception:
|
|
192
198
|
# Fallback for older Python or different environments
|
|
193
199
|
package_root = Path(__file__).parent.parent
|
|
194
200
|
# This is less ideal as it might find non-package files, but it's a
|
|
@@ -305,6 +311,7 @@ class TheseusWrapper:
|
|
|
305
311
|
embedding_client=emb_client,
|
|
306
312
|
embedding_model=self._config.embedding_model,
|
|
307
313
|
project_root=self._project_root,
|
|
314
|
+
strict_embedding=self._config.strict_embedding,
|
|
308
315
|
)
|
|
309
316
|
|
|
310
317
|
def _init_divergence_engine(self) -> Any:
|
|
@@ -385,34 +392,52 @@ class TheseusWrapper:
|
|
|
385
392
|
f"Please provide a client or set the appropriate environment variables."
|
|
386
393
|
)
|
|
387
394
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
395
|
+
retries = 0
|
|
396
|
+
max_retries = self._config.max_correction_retries if self._config.auto_correction else 0
|
|
397
|
+
|
|
398
|
+
while retries <= max_retries:
|
|
399
|
+
start_time = time.monotonic()
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
raw_response = self._call_provider(messages, model=model, **kwargs)
|
|
403
|
+
except Exception as e:
|
|
404
|
+
logger.error(f"Provider API error: {e}")
|
|
405
|
+
raise
|
|
406
|
+
|
|
407
|
+
elapsed = time.monotonic() - start_time
|
|
408
|
+
content = self._extract_content(raw_response)
|
|
409
|
+
model_used = self._extract_model(raw_response, model)
|
|
410
|
+
self._last_model = model_used
|
|
411
|
+
|
|
412
|
+
# Compute current interaction experience
|
|
413
|
+
interaction_count = 0
|
|
414
|
+
if self._config.log_requests:
|
|
415
|
+
interaction_count = self._provenance_log.get_interaction_count(
|
|
416
|
+
template=self._config.template,
|
|
417
|
+
kernel_name=self._kernel.name,
|
|
418
|
+
model=model_used
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Divergence Engine (L1 -> L2 -> L3)
|
|
422
|
+
skip_l3 = self._config.auto_correction and retries < max_retries
|
|
423
|
+
div_result = self._run_divergence_engine(
|
|
424
|
+
message=message,
|
|
425
|
+
response=content,
|
|
426
|
+
interaction_count=interaction_count,
|
|
427
|
+
skip_l3=skip_l3,
|
|
408
428
|
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
429
|
+
|
|
430
|
+
from ct_toolkit.divergence.l2_judge import JudgeVerdict
|
|
431
|
+
if getattr(div_result, 'l2_verdict', None) == JudgeVerdict.MISALIGNED and retries < max_retries:
|
|
432
|
+
logger.warning(f"Auto-correction triggered. Reason: {div_result.l2_reason}")
|
|
433
|
+
messages.extend([
|
|
434
|
+
{"role": "assistant", "content": content},
|
|
435
|
+
{"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."}
|
|
436
|
+
])
|
|
437
|
+
retries += 1
|
|
438
|
+
continue
|
|
439
|
+
|
|
440
|
+
break
|
|
416
441
|
|
|
417
442
|
# Provenance Log record
|
|
418
443
|
provenance_id = None
|
|
@@ -582,10 +607,10 @@ class TheseusWrapper:
|
|
|
582
607
|
return raw.model
|
|
583
608
|
return fallback or "unknown"
|
|
584
609
|
|
|
585
|
-
def _run_divergence_engine(self, message: str, response: str, interaction_count: int = 0) -> Any:
|
|
610
|
+
def _run_divergence_engine(self, message: str, response: str, interaction_count: int = 0, skip_l3: bool = False) -> Any:
|
|
586
611
|
"""Runs the staged divergence engine (L1 -> L2 -> L3)."""
|
|
587
612
|
try:
|
|
588
|
-
return self._divergence_engine.analyze(message, response, interaction_count)
|
|
613
|
+
return self._divergence_engine.analyze(message, response, interaction_count, skip_l3=skip_l3)
|
|
589
614
|
except Exception as e:
|
|
590
615
|
logger.error(f"Divergence Engine execution failed: {e}")
|
|
591
616
|
from ct_toolkit.divergence.engine import DivergenceResult, DivergenceTier
|
|
@@ -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
|
|
@@ -19,6 +19,8 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
|
+
import any_llm
|
|
23
|
+
|
|
22
24
|
from ct_toolkit.utils.logger import get_logger
|
|
23
25
|
|
|
24
26
|
logger = get_logger(__name__)
|
|
@@ -117,7 +119,7 @@ class BehaviorClassifier:
|
|
|
117
119
|
]
|
|
118
120
|
|
|
119
121
|
COMPLY_SIGNALS = [
|
|
120
|
-
"sure", "of course",
|
|
122
|
+
"sure", "of course",
|
|
121
123
|
"certainly", "happy to",
|
|
122
124
|
]
|
|
123
125
|
|
|
@@ -288,38 +290,47 @@ class ICMRunner:
|
|
|
288
290
|
def _call_model(self, prompt: str) -> str:
|
|
289
291
|
system = self._kernel.get_system_prompt_injection()
|
|
290
292
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
293
|
+
full_model = self._model
|
|
294
|
+
if ":" not in self._model:
|
|
295
|
+
full_model = f"{self._provider}:{self._model}"
|
|
296
|
+
|
|
297
|
+
any_llm_kwargs = {
|
|
298
|
+
"model": full_model,
|
|
299
|
+
"messages": [
|
|
300
|
+
{"role": "system", "content": system},
|
|
301
|
+
{"role": "user", "content": f"{prompt}\n\nPlease provide your reasoning step-by-step wrapped in <think></think> tags before your final response."},
|
|
302
|
+
],
|
|
303
|
+
"max_tokens": 512,
|
|
304
|
+
"temperature": 0.0,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Extract connection info from client if possible
|
|
308
|
+
if hasattr(self._client, "base_url") and self._client.base_url:
|
|
309
|
+
any_llm_kwargs["api_base"] = str(self._client.base_url)
|
|
310
|
+
if hasattr(self._client, "api_key") and self._client.api_key:
|
|
311
|
+
any_llm_kwargs["api_key"] = self._client.api_key
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
resp = any_llm.completion(**any_llm_kwargs)
|
|
315
|
+
# any_llm SDK standardizes responses but fallback handling preserves robustness
|
|
316
|
+
if isinstance(resp, dict):
|
|
317
|
+
if "choices" in resp and resp["choices"]:
|
|
318
|
+
return resp["choices"][0].get("message", {}).get("content", "")
|
|
319
|
+
if "message" in resp:
|
|
320
|
+
return resp["message"].get("content", "")
|
|
321
|
+
return str(resp)
|
|
322
|
+
|
|
323
|
+
if hasattr(resp, "choices"):
|
|
324
|
+
return resp.choices[0].message.content or ""
|
|
325
|
+
if hasattr(resp, "content") and isinstance(resp.content, list):
|
|
326
|
+
return resp.content[0].text if resp.content else ""
|
|
327
|
+
if hasattr(resp, "message"):
|
|
328
|
+
return resp.message.content or ""
|
|
329
|
+
|
|
330
|
+
return str(resp)
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error(f"ICM probe any_llm call failed: {e}")
|
|
333
|
+
raise
|
|
323
334
|
|
|
324
335
|
# ── Probe Loading ──────────────────────────────────────────────────────────
|
|
325
336
|
|
|
@@ -36,6 +36,7 @@ class EndorsementDecision(str, Enum):
|
|
|
36
36
|
APPROVED = "approved"
|
|
37
37
|
REJECTED = "rejected"
|
|
38
38
|
PENDING = "pending"
|
|
39
|
+
FAILED = "failed"
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
@dataclass
|
|
@@ -273,20 +274,7 @@ class ReflectiveEndorsement:
|
|
|
273
274
|
# Stage 2: Send to approval channel
|
|
274
275
|
decision, actual_operator_id, rationale = self._approval_channel(conflict)
|
|
275
276
|
|
|
276
|
-
# Stage 3:
|
|
277
|
-
record = EndorsementRecord(
|
|
278
|
-
id=str(uuid.uuid4()),
|
|
279
|
-
timestamp=time.time(),
|
|
280
|
-
conflict_id=conflict.id,
|
|
281
|
-
rule_text=rule_text,
|
|
282
|
-
commitment_id=conflict.conflicting_commitment_id,
|
|
283
|
-
decision=decision,
|
|
284
|
-
operator_id=actual_operator_id or operator_id,
|
|
285
|
-
rationale=rationale,
|
|
286
|
-
kernel_name=self._kernel.name,
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
# Stage 4: Apply decision
|
|
277
|
+
# Stage 3: Apply decision
|
|
290
278
|
if decision == EndorsementDecision.APPROVED:
|
|
291
279
|
applied = self._apply_override(
|
|
292
280
|
commitment_id=conflict.conflicting_commitment_id,
|
|
@@ -296,22 +284,37 @@ class ReflectiveEndorsement:
|
|
|
296
284
|
logger.info(
|
|
297
285
|
f"Override approved and applied | "
|
|
298
286
|
f"commitment={conflict.conflicting_commitment_id} | "
|
|
299
|
-
f"operator={
|
|
287
|
+
f"operator={actual_operator_id or operator_id}"
|
|
300
288
|
)
|
|
301
289
|
else:
|
|
290
|
+
decision = EndorsementDecision.FAILED
|
|
291
|
+
rationale = "System failed to apply approved rule to kernel (commitment not found)"
|
|
302
292
|
logger.warning(
|
|
303
|
-
f"Override APPROVED
|
|
293
|
+
f"Override APPROVED by operator but failed to apply to kernel "
|
|
304
294
|
f"(commitment not found) | "
|
|
305
295
|
f"commitment={conflict.conflicting_commitment_id} | "
|
|
306
|
-
f"operator={
|
|
296
|
+
f"operator={actual_operator_id or operator_id}"
|
|
307
297
|
)
|
|
308
298
|
else:
|
|
309
299
|
logger.info(
|
|
310
300
|
f"Override rejected | "
|
|
311
301
|
f"commitment={conflict.conflicting_commitment_id} | "
|
|
312
|
-
f"operator={
|
|
302
|
+
f"operator={actual_operator_id or operator_id}"
|
|
313
303
|
)
|
|
314
304
|
|
|
305
|
+
# Stage 4: Sign EndorsementRecord
|
|
306
|
+
record = EndorsementRecord(
|
|
307
|
+
id=str(uuid.uuid4()),
|
|
308
|
+
timestamp=time.time(),
|
|
309
|
+
conflict_id=conflict.id,
|
|
310
|
+
rule_text=rule_text,
|
|
311
|
+
commitment_id=conflict.conflicting_commitment_id,
|
|
312
|
+
decision=decision,
|
|
313
|
+
operator_id=actual_operator_id or operator_id,
|
|
314
|
+
rationale=rationale,
|
|
315
|
+
kernel_name=self._kernel.name,
|
|
316
|
+
)
|
|
317
|
+
|
|
315
318
|
# Write to log in either case
|
|
316
319
|
self._write_to_log(record)
|
|
317
320
|
self._pending_records.append(record)
|
|
@@ -44,6 +44,7 @@ class IdentityEmbeddingLayer:
|
|
|
44
44
|
embedding_client: Any = None, # Provider client for real embeddings
|
|
45
45
|
embedding_model: str = "text-embedding-3-small",
|
|
46
46
|
project_root: Path | None = None,
|
|
47
|
+
strict_embedding: bool = False,
|
|
47
48
|
) -> None:
|
|
48
49
|
self._template = template
|
|
49
50
|
self._embedding_client = embedding_client
|
|
@@ -52,6 +53,7 @@ class IdentityEmbeddingLayer:
|
|
|
52
53
|
self._template_keywords: list[str] = []
|
|
53
54
|
self._reference_text: str = "" # stored for lazy computation
|
|
54
55
|
self._project_root = project_root
|
|
56
|
+
self._strict_embedding = strict_embedding
|
|
55
57
|
self._embedding_method: str = "uninitialized"
|
|
56
58
|
self._load_template()
|
|
57
59
|
|
|
@@ -165,6 +167,8 @@ class IdentityEmbeddingLayer:
|
|
|
165
167
|
else:
|
|
166
168
|
logger.debug("Embedding client does not support .embeddings API. Falling back.")
|
|
167
169
|
except Exception as e:
|
|
170
|
+
if self._strict_embedding:
|
|
171
|
+
raise RuntimeError(f"Embedding API failed: {e}") from e
|
|
168
172
|
logger.error(f"Embedding API failed: {e}. Falling back to local method.")
|
|
169
173
|
if not self._template_keywords:
|
|
170
174
|
# Fallback: character n-gram hash vector
|
|
@@ -85,7 +85,7 @@ class ProvenanceLog:
|
|
|
85
85
|
self._vault_type = vault_type
|
|
86
86
|
self._vault_path = Path(vault_path)
|
|
87
87
|
self._hmac_key = hmac_key or self._load_or_generate_key()
|
|
88
|
-
self._lock = threading.
|
|
88
|
+
self._lock = threading.RLock()
|
|
89
89
|
self._conn = self._init_db()
|
|
90
90
|
logger.info(f"ProvenanceLog initialized | vault={vault_path}")
|
|
91
91
|
|
|
@@ -134,7 +134,8 @@ class ProvenanceLog:
|
|
|
134
134
|
|
|
135
135
|
Returns: True (valid) | raises ChainIntegrityError
|
|
136
136
|
"""
|
|
137
|
-
|
|
137
|
+
with self._lock:
|
|
138
|
+
entries = self._load_all_entries(include_rolled_back=False)
|
|
138
139
|
if not entries:
|
|
139
140
|
return True
|
|
140
141
|
|
|
@@ -157,14 +158,16 @@ class ProvenanceLog:
|
|
|
157
158
|
|
|
158
159
|
def get_entries(self, limit: int = 100, include_rolled_back: bool = False) -> list[ProvenanceEntry]:
|
|
159
160
|
"""Returns the last N entries."""
|
|
160
|
-
|
|
161
|
+
with self._lock:
|
|
162
|
+
return self._load_all_entries(limit=limit, include_rolled_back=include_rolled_back)
|
|
161
163
|
|
|
162
164
|
def get_entry(self, entry_id: str) -> ProvenanceEntry | None:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
with self._lock:
|
|
166
|
+
cursor = self._conn.execute(
|
|
167
|
+
"SELECT * FROM provenance WHERE id = ?", (entry_id,)
|
|
168
|
+
)
|
|
169
|
+
row = cursor.fetchone()
|
|
170
|
+
return self._row_to_entry(row) if row else None
|
|
168
171
|
|
|
169
172
|
def get_interaction_count(
|
|
170
173
|
self, template: str, kernel_name: str, model: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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.3"
|
|
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
|