ct-toolkit 0.3.2__tar.gz → 0.3.4__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.2 → ct_toolkit-0.3.4}/PKG-INFO +1 -1
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/__init__.py +0 -1
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/wrapper.py +5 -2
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/l3_icm.py +44 -33
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/endorsement/reflective.py +21 -18
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/embedding.py +4 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/provenance/log.py +11 -8
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit.egg-info/PKG-INFO +1 -1
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/pyproject.toml +1 -1
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/LICENSE +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/README.md +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit.egg-info/SOURCES.txt +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/setup.cfg +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.2 → ct_toolkit-0.3.4}/tests/test_summarization_drift.py +0 -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
|
|
@@ -69,6 +69,8 @@ class WrapperConfig:
|
|
|
69
69
|
elasticity_growth_rate: float | None = None
|
|
70
70
|
risk_profile: RiskProfile | None = None
|
|
71
71
|
|
|
72
|
+
strict_embedding: bool = False # Raise error if embedding API fails
|
|
73
|
+
|
|
72
74
|
|
|
73
75
|
@dataclass
|
|
74
76
|
class CTResponse:
|
|
@@ -192,7 +194,7 @@ class TheseusWrapper:
|
|
|
192
194
|
if file_path.is_file():
|
|
193
195
|
self._integrity_monitor.register_file(file_path)
|
|
194
196
|
|
|
195
|
-
except
|
|
197
|
+
except Exception:
|
|
196
198
|
# Fallback for older Python or different environments
|
|
197
199
|
package_root = Path(__file__).parent.parent
|
|
198
200
|
# This is less ideal as it might find non-package files, but it's a
|
|
@@ -309,6 +311,7 @@ class TheseusWrapper:
|
|
|
309
311
|
embedding_client=emb_client,
|
|
310
312
|
embedding_model=self._config.embedding_model,
|
|
311
313
|
project_root=self._project_root,
|
|
314
|
+
strict_embedding=self._config.strict_embedding,
|
|
312
315
|
)
|
|
313
316
|
|
|
314
317
|
def _init_divergence_engine(self) -> Any:
|
|
@@ -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
|
|
@@ -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.4"
|
|
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
|