ct-toolkit 0.3.16__tar.gz → 0.3.18__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.
Files changed (59) hide show
  1. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/PKG-INFO +3 -3
  2. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/README.md +2 -2
  3. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/__init__.py +1 -1
  4. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/l2_judge.py +46 -25
  5. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/l3_icm.py +104 -34
  6. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/PKG-INFO +3 -3
  7. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/pyproject.toml +1 -1
  8. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/LICENSE +0 -0
  9. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/__main__.py +0 -0
  10. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/cli.py +0 -0
  11. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/__init__.py +0 -0
  12. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/compatibility.py +0 -0
  13. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/compression_guard.py +0 -0
  14. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/exceptions.py +0 -0
  15. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/integrity.py +0 -0
  16. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/kernel.py +0 -0
  17. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/wrapper.py +0 -0
  18. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/__init__.py +0 -0
  19. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/analysis.py +0 -0
  20. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/engine.py +0 -0
  21. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/loss.py +0 -0
  22. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/scheduler.py +0 -0
  23. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/__init__.py +0 -0
  24. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/probes/__init__.py +0 -0
  25. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/reflective.py +0 -0
  26. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/__init__.py +0 -0
  27. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/embedding.py +0 -0
  28. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/__init__.py +0 -0
  29. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/defense.yaml +0 -0
  30. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/finance.yaml +0 -0
  31. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/general.yaml +0 -0
  32. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/medical.yaml +0 -0
  33. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/__init__.py +0 -0
  34. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/default.yaml +0 -0
  35. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/defense.yaml +0 -0
  36. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/__init__.py +0 -0
  37. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/autogen.py +0 -0
  38. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/crewai.py +0 -0
  39. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/deepagents.py +0 -0
  40. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/langchain.py +0 -0
  41. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/litellm.py +0 -0
  42. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/__init__.py +0 -0
  43. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/log.py +0 -0
  44. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/vault/__init__.py +0 -0
  45. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/server.py +0 -0
  46. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/utils/__init__.py +0 -0
  47. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/utils/logger.py +0 -0
  48. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/SOURCES.txt +0 -0
  49. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/dependency_links.txt +0 -0
  50. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/entry_points.txt +0 -0
  51. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/requires.txt +0 -0
  52. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/top_level.txt +0 -0
  53. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/setup.cfg +0 -0
  54. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_analysis.py +0 -0
  55. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_cli.py +0 -0
  56. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_cli_extended.py +0 -0
  57. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_deep_agents_integration.py +0 -0
  58. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_loss.py +0 -0
  59. {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/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.16
3
+ Version: 0.3.18
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -120,9 +120,9 @@ ct-toolkit list-templates
120
120
 
121
121
  | Metric | Status |
122
122
  | :--------------- | :------------------------------------------------------ |
123
- | **Tests** | ✅ 293 passed, 3 skipped, 13 warnings (93% coverage) |
123
+ | **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
124
124
  | **Downloads** | [![PyPI Downloads](https://static.pepy.tech/personalized-badge/ct-toolkit?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=MAGENTA&left_text=downloads)](https://pepy.tech/projects/ct-toolkit) |
125
- | **Last Phase** | ✅ v0.3.16: Added custom judge_model and fixed Ollama L3 runner |
125
+ | **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
126
126
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
127
127
 
128
128
  ### Framework & Model Support
@@ -77,9 +77,9 @@ ct-toolkit list-templates
77
77
 
78
78
  | Metric | Status |
79
79
  | :--------------- | :------------------------------------------------------ |
80
- | **Tests** | ✅ 293 passed, 3 skipped, 13 warnings (93% coverage) |
80
+ | **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
81
81
  | **Downloads** | [![PyPI Downloads](https://static.pepy.tech/personalized-badge/ct-toolkit?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=MAGENTA&left_text=downloads)](https://pepy.tech/projects/ct-toolkit) |
82
- | **Last Phase** | ✅ v0.3.16: Added custom judge_model and fixed Ollama L3 runner |
82
+ | **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
83
83
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
84
84
 
85
85
  ### Framework & Model Support
@@ -23,7 +23,7 @@ from ct_toolkit.core.exceptions import (
23
23
  ChainIntegrityError,
24
24
  )
25
25
 
26
- __version__ = "0.3.16"
26
+ __version__ = "0.3.18"
27
27
 
28
28
  __all__ = [
29
29
  "TheseusWrapper",
@@ -19,6 +19,7 @@ from enum import Enum
19
19
  from typing import Any
20
20
  from pydantic import BaseModel, Field
21
21
  import instructor
22
+ import litellm
22
23
  from jinja2 import Template
23
24
 
24
25
  from ct_toolkit.utils.logger import get_logger
@@ -103,26 +104,22 @@ class LLMJudge:
103
104
  self._provider = provider
104
105
  self._model = model or self._default_model(provider)
105
106
 
106
- # Fix for Ollama: Strip /v1 from base_url if present
107
+ # Fix for Ollama: LiteLLM's native Ollama provider expects root port (no /v1)
108
+ self._api_base = None
107
109
  if provider == "ollama" and hasattr(client, "base_url"):
108
110
  base_url = str(client.base_url).rstrip("/")
109
111
  if base_url.endswith("/v1"):
110
- new_base = base_url[:-3]
111
- logger.debug(f"Stripping /v1 from Ollama judge base_url: {base_url} -> {new_base}")
112
- try:
113
- import httpx
114
- client.base_url = httpx.URL(new_base)
115
- except ImportError:
116
- client.base_url = new_base
117
-
118
- # Patch client with instructor based on provider
119
- if provider == "openai":
120
- self._instructor_client = instructor.from_openai(client)
121
- elif provider == "anthropic":
122
- self._instructor_client = instructor.from_anthropic(client)
123
- else:
124
- # Fallback to standard patch for other providers (e.g. Ollama)
125
- self._instructor_client = instructor.patch(client)
112
+ self._api_base = base_url[:-3]
113
+ logger.debug(f"Stripping /v1 from Ollama judge api_base: {base_url} -> {self._api_base}")
114
+ else:
115
+ self._api_base = base_url
116
+ elif hasattr(client, "base_url"):
117
+ self._api_base = str(client.base_url)
118
+
119
+ self._api_key = getattr(client, "api_key", None)
120
+
121
+ # Use instructor with litellm for unified provider support
122
+ self._instructor_client = instructor.from_litellm(litellm.completion)
126
123
 
127
124
  @staticmethod
128
125
  def _default_model(provider: str) -> str:
@@ -149,27 +146,51 @@ class LLMJudge:
149
146
  request_text=request_text[:1000],
150
147
  )
151
148
 
149
+ full_model = self._model
150
+ # Prevent colon-to-slash replacement if model already has ollama/ prefix
151
+ # or if the provider is specifically ollama
152
+ is_ollama = (self._provider == "ollama" or self._model.startswith("ollama/"))
153
+
154
+ if ":" in self._model and not is_ollama:
155
+ full_model = self._model.replace(":", "/", 1)
156
+ elif self._provider not in ("openai", "unknown") and not is_ollama:
157
+ if not self._model.startswith(f"{self._provider}/"):
158
+ full_model = f"{self._provider}/{self._model}"
159
+
160
+ # Ensure ollama always has prefix if it has a colon
161
+ if self._provider == "ollama" and ":" in self._model and not full_model.startswith("ollama/"):
162
+ full_model = f"ollama/{full_model}"
163
+
152
164
  try:
165
+ kwargs: dict[str, Any] = {
166
+ "model": full_model,
167
+ "response_model": JudgeResponse,
168
+ "max_retries": 2,
169
+ }
170
+
171
+ if self._api_base:
172
+ kwargs["api_base"] = self._api_base
173
+ if self._api_key:
174
+ kwargs["api_key"] = self._api_key
175
+
153
176
  # Call using instructor for structured data
154
177
  if self._provider == "anthropic":
155
- data: JudgeResponse = self._instructor_client.messages.create(
156
- model=self._model,
157
- response_model=JudgeResponse,
158
- system=system_prompt,
178
+ # Anthropic LiteLLM requires system prompt in extra_body or via specific messages call
179
+ # But instructor.from_litellm handles standard messages list
180
+ data: JudgeResponse = self._instructor_client.chat.completions.create(
181
+ **kwargs,
159
182
  messages=[
183
+ {"role": "system", "content": system_prompt},
160
184
  {"role": "user", "content": user_prompt},
161
185
  ],
162
- max_retries=2
163
186
  )
164
187
  else:
165
188
  data: JudgeResponse = self._instructor_client.chat.completions.create(
166
- model=self._model,
167
- response_model=JudgeResponse,
189
+ **kwargs,
168
190
  messages=[
169
191
  {"role": "system", "content": system_prompt},
170
192
  {"role": "user", "content": user_prompt},
171
193
  ],
172
- max_retries=2
173
194
  )
174
195
 
175
196
  result = JudgeResult(
@@ -19,13 +19,23 @@ from dataclasses import dataclass, field
19
19
  from pathlib import Path
20
20
  from typing import Any
21
21
 
22
+ import instructor
22
23
  import litellm
24
+ from pydantic import BaseModel, Field
23
25
 
24
26
  from ct_toolkit.utils.logger import get_logger
25
27
 
26
28
  logger = get_logger(__name__)
27
29
 
28
30
 
31
+ # ── Pydantic Models for Structured Responses ────────────────────────────────
32
+
33
+ class ProbeResponse(BaseModel):
34
+ """Pydantic model for structured probe response with graceful fallback."""
35
+ content: str = Field(..., description="Model's response text")
36
+ reasoning: str = Field(default="", description="Model's reasoning if provided")
37
+
38
+
29
39
  # ── Data Models ─────────────────────────────────────────────────────────────
30
40
 
31
41
  @dataclass
@@ -221,6 +231,9 @@ class ICMRunner:
221
231
  self._include_domain = include_domain_probes
222
232
  self._project_root = project_root
223
233
  self._max_probes = max_probes
234
+
235
+ # Use instructor with litellm for structured response validation
236
+ self._instructor_client = instructor.from_litellm(litellm.completion)
224
237
 
225
238
  @staticmethod
226
239
  def _default_model(provider: str) -> str:
@@ -322,35 +335,93 @@ class ICMRunner:
322
335
  # ── Model Call ──────────────────────────────────────────────────────────
323
336
 
324
337
  def _call_model(self, prompt: str) -> str:
338
+ """
339
+ Calls model with structured response validation and graceful fallback.
340
+
341
+ L3 Graceful Fallback Strategy:
342
+ 1. Try structured call via instructor (validates against ProbeResponse schema)
343
+ 2. If validation fails (e.g., JSON parse error), fall back to raw completion
344
+ 3. If raw completion fails, return safe error placeholder
345
+
346
+ This mirrors L2 Judge's approach, preventing "parse failure = hard error" behavior.
347
+ """
325
348
  system = self._kernel.get_system_prompt_injection()
349
+ full_model = self._format_model_name()
350
+
351
+ user_content = (
352
+ f"{prompt}\n\n"
353
+ "Please provide your reasoning step-by-step wrapped in <think></think> tags "
354
+ "before your final response."
355
+ )
356
+
357
+ kwargs = self._build_litellm_kwargs(full_model, system, user_content)
326
358
 
327
- # LiteLLM format
359
+ # ── Attempt 1: Structured call with instructor validation ────────
360
+ try:
361
+ logger.debug(f"L3 ICM attempting structured validation for model {full_model}")
362
+ response: ProbeResponse = self._instructor_client.chat.completions.create(
363
+ **kwargs,
364
+ response_model=ProbeResponse,
365
+ max_retries=1,
366
+ )
367
+
368
+ # Combine reasoning and content into full response
369
+ full_response = f"<think>{response.reasoning}</think>\n{response.content}" \
370
+ if response.reasoning else response.content
371
+ logger.debug(f"L3 ICM structured call succeeded")
372
+ return full_response
373
+
374
+ except Exception as e:
375
+ logger.warning(
376
+ f"L3 ICM structured validation failed (likely JSON/tool-call parse error): {e}. "
377
+ f"Falling back to raw completion."
378
+ )
379
+
380
+ # ── Attempt 2: Raw litellm.completion without structured validation ────────
381
+ try:
382
+ resp = litellm.completion(**kwargs)
383
+ content = self._extract_response_content(resp)
384
+ logger.info(f"L3 ICM raw fallback succeeded")
385
+ return content
386
+
387
+ except Exception as fallback_e:
388
+ # ── Attempt 3: Safe error response ────────
389
+ logger.error(
390
+ f"L3 ICM both structured and raw calls failed: "
391
+ f"structured={type(e).__name__}, raw={type(fallback_e).__name__}. "
392
+ f"Returning error placeholder for graceful degradation."
393
+ )
394
+ return "[ERROR: Model call failed, unable to assess response]"
395
+
396
+ def _format_model_name(self) -> str:
397
+ """Format model name according to provider conventions."""
328
398
  full_model = self._model
329
- # Prevent colon-to-slash replacement if model already has ollama/ prefix
330
- # or if the provider is specifically ollama
331
399
  is_ollama = (self._provider == "ollama" or self._model.startswith("ollama/"))
332
-
400
+
333
401
  if ":" in self._model and not is_ollama:
334
- full_model = self._model.replace(":", "/", 1)
402
+ full_model = self._model.replace(":", "/", 1)
335
403
  elif self._provider not in ("openai", "unknown") and not is_ollama:
336
- if not self._model.startswith(f"{self._provider}/"):
337
- full_model = f"{self._provider}/{self._model}"
338
-
339
- # Ensure ollama always has prefix if it has a colon
404
+ if not self._model.startswith(f"{self._provider}/"):
405
+ full_model = f"{self._provider}/{self._model}"
406
+
340
407
  if self._provider == "ollama" and ":" in self._model and not full_model.startswith("ollama/"):
341
- full_model = f"ollama/{full_model}"
408
+ full_model = f"ollama/{full_model}"
409
+
410
+ return full_model
342
411
 
412
+ def _build_litellm_kwargs(self, full_model: str, system: str, user_content: str) -> dict[str, Any]:
413
+ """Build kwargs for litellm call."""
343
414
  kwargs: dict[str, Any] = {
344
415
  "model": full_model,
345
416
  "messages": [
346
417
  {"role": "system", "content": system},
347
- {"role": "user", "content": f"{prompt}\n\nPlease provide your reasoning step-by-step wrapped in <think></think> tags before your final response."},
418
+ {"role": "user", "content": user_content},
348
419
  ],
349
420
  "max_tokens": 512,
350
421
  "temperature": 0.0,
351
422
  }
352
-
353
- # Extract connection info from client if possible
423
+
424
+ # Extract connection info from client
354
425
  if hasattr(self._client, "base_url") and self._client.base_url:
355
426
  api_base = str(self._client.base_url).rstrip("/")
356
427
  if self._provider == "ollama" and api_base.endswith("/v1"):
@@ -358,31 +429,30 @@ class ICMRunner:
358
429
  logger.debug(f"Stripping /v1 from Ollama api_base (ICM): {api_base} -> {new_base}")
359
430
  api_base = new_base
360
431
  kwargs["api_base"] = api_base
361
-
432
+
362
433
  if hasattr(self._client, "api_key") and self._client.api_key:
363
434
  kwargs["api_key"] = self._client.api_key
364
-
365
- try:
366
- resp = litellm.completion(**kwargs)
367
-
368
- # Extract content from LiteLLM response
369
- if hasattr(resp, "choices") and resp.choices:
370
- choice = resp.choices[0]
371
- if hasattr(choice, "message") and choice.message:
372
- return choice.message.content or ""
373
-
374
- # Fallback
375
- if isinstance(resp, dict):
376
- if "choices" in resp and resp["choices"]:
377
- return resp["choices"][0].get("message", {}).get("content", "")
378
- if "message" in resp:
379
- return resp["message"].get("content", "")
380
- return str(resp)
435
+
436
+ return kwargs
381
437
 
438
+ @staticmethod
439
+ def _extract_response_content(resp: Any) -> str:
440
+ """Extract text content from litellm response object."""
441
+ # Handle object response with .choices attribute
442
+ if hasattr(resp, "choices") and resp.choices:
443
+ choice = resp.choices[0]
444
+ if hasattr(choice, "message") and choice.message:
445
+ return choice.message.content or ""
446
+
447
+ # Handle dict response
448
+ if isinstance(resp, dict):
449
+ if "choices" in resp and resp["choices"]:
450
+ return resp["choices"][0].get("message", {}).get("content", "")
451
+ if "message" in resp:
452
+ return resp["message"].get("content", "")
382
453
  return str(resp)
383
- except Exception as e:
384
- logger.error(f"ICM probe litellm call failed: {e}")
385
- raise
454
+
455
+ return str(resp)
386
456
 
387
457
  # ── Probe Loading ──────────────────────────────────────────────────────────
388
458
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.3.16
3
+ Version: 0.3.18
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -120,9 +120,9 @@ ct-toolkit list-templates
120
120
 
121
121
  | Metric | Status |
122
122
  | :--------------- | :------------------------------------------------------ |
123
- | **Tests** | ✅ 293 passed, 3 skipped, 13 warnings (93% coverage) |
123
+ | **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
124
124
  | **Downloads** | [![PyPI Downloads](https://static.pepy.tech/personalized-badge/ct-toolkit?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=MAGENTA&left_text=downloads)](https://pepy.tech/projects/ct-toolkit) |
125
- | **Last Phase** | ✅ v0.3.16: Added custom judge_model and fixed Ollama L3 runner |
125
+ | **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
126
126
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
127
127
 
128
128
  ### Framework & Model Support
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ct-toolkit"
7
- version = "0.3.16"
7
+ version = "0.3.18"
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