ct-toolkit 0.1.7__tar.gz → 0.2.0__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 (35) hide show
  1. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/PKG-INFO +43 -6
  2. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/README.md +34 -5
  3. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/core/kernel.py +75 -1
  4. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/core/wrapper.py +38 -0
  5. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/divergence/engine.py +7 -2
  6. ct_toolkit-0.2.0/ct_toolkit/middleware/__init__.py +5 -0
  7. ct_toolkit-0.2.0/ct_toolkit/middleware/autogen.py +215 -0
  8. ct_toolkit-0.2.0/ct_toolkit/middleware/crewai.py +162 -0
  9. ct_toolkit-0.2.0/ct_toolkit/middleware/langchain.py +255 -0
  10. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit.egg-info/PKG-INFO +43 -6
  11. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit.egg-info/SOURCES.txt +4 -0
  12. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit.egg-info/requires.txt +12 -0
  13. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/pyproject.toml +12 -2
  14. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/LICENSE +0 -0
  15. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/__init__.py +0 -0
  16. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/core/__init__.py +0 -0
  17. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/core/compatibility.py +0 -0
  18. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/core/exceptions.py +0 -0
  19. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/divergence/__init__.py +0 -0
  20. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/divergence/l2_judge.py +0 -0
  21. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/divergence/l3_icm.py +0 -0
  22. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/divergence/scheduler.py +0 -0
  23. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/endorsement/__init__.py +0 -0
  24. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/endorsement/probes/__init__.py +0 -0
  25. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/endorsement/reflective.py +0 -0
  26. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/identity/__init__.py +0 -0
  27. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/identity/embedding.py +0 -0
  28. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/provenance/__init__.py +0 -0
  29. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/provenance/log.py +0 -0
  30. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/provenance/vault/__init__.py +0 -0
  31. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/utils/__init__.py +0 -0
  32. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit/utils/logger.py +0 -0
  33. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit.egg-info/dependency_links.txt +0 -0
  34. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/ct_toolkit.egg-info/top_level.txt +0 -0
  35. {ct_toolkit-0.1.7 → ct_toolkit-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -20,6 +20,14 @@ Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: jinja2>=3.1.0
21
21
  Provides-Extra: ollama
22
22
  Requires-Dist: ollama>=0.2.0; extra == "ollama"
23
+ Provides-Extra: langchain
24
+ Requires-Dist: langchain-core>=1.2.0; extra == "langchain"
25
+ Provides-Extra: crewai
26
+ Requires-Dist: crewai>=1.10.0; extra == "crewai"
27
+ Provides-Extra: autogen
28
+ Requires-Dist: pyautogen>=0.4.0; extra == "autogen"
29
+ Provides-Extra: docs
30
+ Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
23
31
  Provides-Extra: dev
24
32
  Requires-Dist: pytest>=8.0; extra == "dev"
25
33
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -68,7 +76,6 @@ pip install ct-toolkit
68
76
  from ct_toolkit import TheseusWrapper
69
77
 
70
78
  # Single line change — the rest is automatic
71
- # Initialize by just passing the provider name
72
79
  client = TheseusWrapper(provider="openai")
73
80
 
74
81
  # Standard chat interface
@@ -79,6 +86,38 @@ print(f"Divergence score : {response.divergence_score:.4f}")
79
86
  print(f"Tier : {response.divergence_tier}")
80
87
  ```
81
88
 
89
+ ### Framework Middleware
90
+
91
+ CT Toolkit provides first-class integrations for the most popular agentic frameworks.
92
+
93
+ #### LangChain
94
+ ```python
95
+ from ct_toolkit.middleware.langchain import TheseusChatModel
96
+
97
+ llm = TheseusChatModel(provider="openai", model="gpt-4o")
98
+ response = llm.invoke("What is identity continuity?")
99
+ ```
100
+
101
+ #### CrewAI
102
+ ```python
103
+ from ct_toolkit.middleware.crewai import TheseusCrewMiddleware
104
+ from crewai import Crew
105
+
106
+ crew = Crew(agents=[...], tasks=[...])
107
+ # Automatically wraps all agent LLMs with parent-kernel guardrails
108
+ TheseusCrewMiddleware.apply_to_crew(crew, manager_wrapper)
109
+ ```
110
+
111
+ #### AutoGen
112
+ ```python
113
+ from ct_toolkit.middleware.autogen import TheseusAutoGenMiddleware
114
+ from autogen import ConversableAgent
115
+
116
+ agent = ConversableAgent("assistant", llm_config={...})
117
+ # Injects incoming validation and outgoing divergence analysis
118
+ TheseusAutoGenMiddleware.apply_to_agent(agent, wrapper)
119
+ ```
120
+
82
121
  ---
83
122
 
84
123
  ## Integration Models
@@ -263,9 +302,7 @@ ct_toolkit/
263
302
  │ ├── engine.py # L1→L2→L3 orchestration
264
303
  │ ├── l2_judge.py # LLM-as-judge
265
304
  │ └── l3_icm.py # ICM Probe Battery
266
- ├── endorsement/
267
- │ ├── reflective.py # Reflective Endorsement protocol
268
- │ └── probes/ # Ethical scenario test batteries
305
+ ├── middleware/ # Framework Integrations (LangChain, CrewAI, AutoGen)
269
306
  ├── identity/
270
307
  │ ├── embedding.py # ECS — cosine similarity
271
308
  │ └── templates/ # Domain identity templates
@@ -284,10 +321,10 @@ CT Toolkit is an active engineering effort implementing the paper's framework ac
284
321
 
285
322
  - **Phase 0 — MVP Core Infrastructure:** Constitutional kernel, reflective endorsement, provenance log, full template/kernel compatibility matrix, OpenAI/Anthropic/Ollama provider support.
286
323
  - **Phase 1 — Identity Continuity Mechanisms:** L1/L2/L3 divergence engine, real embedding API integration, Stability-Plasticity Scheduling via `ElasticityScheduler` + `RiskProfile`.
324
+ - **Phase 2 — Multi-Agent Hierarchy Support:** Hierarchical kernel propagation, cascade-blocking, LangChain/CrewAI/AutoGen integration.
287
325
 
288
326
  ### Future Roadmap
289
327
 
290
- - **Phase 2:** Multi-Agent Hierarchy Support (hierarchical kernel propagation, LangChain/CrewAI/AutoGen integration).
291
328
  - **Phase 3:** ICM and Measurement Infrastructure (reasoning chain analysis, policy-drift measurement, cross-checkpoint comparison).
292
329
  - **Phase 4:** Open-Source Model Support (divergence penalty loss function, Llama/Mistral/Phi fine-tune integration).
293
330
  - **Phase 5:** Vault and Security Infrastructure (cloud vault adapter, rollback mechanism, HashiCorp Vault).
@@ -39,7 +39,6 @@ pip install ct-toolkit
39
39
  from ct_toolkit import TheseusWrapper
40
40
 
41
41
  # Single line change — the rest is automatic
42
- # Initialize by just passing the provider name
43
42
  client = TheseusWrapper(provider="openai")
44
43
 
45
44
  # Standard chat interface
@@ -50,6 +49,38 @@ print(f"Divergence score : {response.divergence_score:.4f}")
50
49
  print(f"Tier : {response.divergence_tier}")
51
50
  ```
52
51
 
52
+ ### Framework Middleware
53
+
54
+ CT Toolkit provides first-class integrations for the most popular agentic frameworks.
55
+
56
+ #### LangChain
57
+ ```python
58
+ from ct_toolkit.middleware.langchain import TheseusChatModel
59
+
60
+ llm = TheseusChatModel(provider="openai", model="gpt-4o")
61
+ response = llm.invoke("What is identity continuity?")
62
+ ```
63
+
64
+ #### CrewAI
65
+ ```python
66
+ from ct_toolkit.middleware.crewai import TheseusCrewMiddleware
67
+ from crewai import Crew
68
+
69
+ crew = Crew(agents=[...], tasks=[...])
70
+ # Automatically wraps all agent LLMs with parent-kernel guardrails
71
+ TheseusCrewMiddleware.apply_to_crew(crew, manager_wrapper)
72
+ ```
73
+
74
+ #### AutoGen
75
+ ```python
76
+ from ct_toolkit.middleware.autogen import TheseusAutoGenMiddleware
77
+ from autogen import ConversableAgent
78
+
79
+ agent = ConversableAgent("assistant", llm_config={...})
80
+ # Injects incoming validation and outgoing divergence analysis
81
+ TheseusAutoGenMiddleware.apply_to_agent(agent, wrapper)
82
+ ```
83
+
53
84
  ---
54
85
 
55
86
  ## Integration Models
@@ -234,9 +265,7 @@ ct_toolkit/
234
265
  │ ├── engine.py # L1→L2→L3 orchestration
235
266
  │ ├── l2_judge.py # LLM-as-judge
236
267
  │ └── l3_icm.py # ICM Probe Battery
237
- ├── endorsement/
238
- │ ├── reflective.py # Reflective Endorsement protocol
239
- │ └── probes/ # Ethical scenario test batteries
268
+ ├── middleware/ # Framework Integrations (LangChain, CrewAI, AutoGen)
240
269
  ├── identity/
241
270
  │ ├── embedding.py # ECS — cosine similarity
242
271
  │ └── templates/ # Domain identity templates
@@ -255,10 +284,10 @@ CT Toolkit is an active engineering effort implementing the paper's framework ac
255
284
 
256
285
  - **Phase 0 — MVP Core Infrastructure:** Constitutional kernel, reflective endorsement, provenance log, full template/kernel compatibility matrix, OpenAI/Anthropic/Ollama provider support.
257
286
  - **Phase 1 — Identity Continuity Mechanisms:** L1/L2/L3 divergence engine, real embedding API integration, Stability-Plasticity Scheduling via `ElasticityScheduler` + `RiskProfile`.
287
+ - **Phase 2 — Multi-Agent Hierarchy Support:** Hierarchical kernel propagation, cascade-blocking, LangChain/CrewAI/AutoGen integration.
258
288
 
259
289
  ### Future Roadmap
260
290
 
261
- - **Phase 2:** Multi-Agent Hierarchy Support (hierarchical kernel propagation, LangChain/CrewAI/AutoGen integration).
262
291
  - **Phase 3:** ICM and Measurement Infrastructure (reasoning chain analysis, policy-drift measurement, cross-checkpoint comparison).
263
292
  - **Phase 4:** Open-Source Model Support (divergence penalty loss function, Llama/Mistral/Phi fine-tune integration).
264
293
  - **Phase 5:** Vault and Security Infrastructure (cloud vault adapter, rollback mechanism, HashiCorp Vault).
@@ -14,6 +14,7 @@ from typing import Any
14
14
  from ct_toolkit.core.exceptions import (
15
15
  AxiomaticViolationError,
16
16
  PlasticConflictError,
17
+ CTToolkitError,
17
18
  )
18
19
 
19
20
 
@@ -59,8 +60,9 @@ class ConstitutionalKernel:
59
60
  kernel.validate_user_rule("allow classified data sharing")
60
61
  """
61
62
 
62
- def __init__(self, profile: KernelProfile) -> None:
63
+ def __init__(self, profile: KernelProfile, is_readonly: bool = False) -> None:
63
64
  self._profile = profile
65
+ self.is_readonly = is_readonly
64
66
 
65
67
  @classmethod
66
68
  def from_yaml(cls, path) -> ConstitutionalKernel:
@@ -127,12 +129,84 @@ class ConstitutionalKernel:
127
129
  return any(kw.lower() in rule_lower for kw in keywords)
128
130
 
129
131
  def update_commitment(self, commitment_id: str, new_value: Any) -> None:
132
+ if self.is_readonly:
133
+ raise CTToolkitError(f"Cannot update commitment '{commitment_id}': Kernel is read-only.")
130
134
  for c in self._profile.plastic_commitments:
131
135
  if c.id == commitment_id:
132
136
  c.current_value = new_value
133
137
  return
134
138
  raise KeyError(f"Commitment not found: '{commitment_id}'")
135
139
 
140
+ def to_dict(self) -> dict[str, Any]:
141
+ """Serializes the kernel to a dictionary."""
142
+ return {
143
+ "name": self._profile.name,
144
+ "version": self._profile.version,
145
+ "description": self._profile.description,
146
+ "axiomatic_anchors": [
147
+ {"id": a.id, "description": a.description, "keywords": a.keywords}
148
+ for a in self.anchors
149
+ ],
150
+ "plastic_commitments": [
151
+ {
152
+ "id": c.id,
153
+ "description": c.description,
154
+ "default_value": c.default_value,
155
+ "current_value": c.current_value,
156
+ "keywords": c.keywords
157
+ }
158
+ for c in self.commitments
159
+ ],
160
+ "is_readonly": self.is_readonly
161
+ }
162
+
163
+ @classmethod
164
+ def from_dict(cls, data: dict[str, Any]) -> ConstitutionalKernel:
165
+ """Creates a kernel from a dictionary."""
166
+ anchors = [AxiomaticAnchor(**a) for a in data.get("axiomatic_anchors", [])]
167
+ commitments = [PlasticCommitment(**c) for c in data.get("plastic_commitments", [])]
168
+
169
+ profile = KernelProfile(
170
+ name=data["name"],
171
+ version=data.get("version", "1.0.0"),
172
+ description=data.get("description", ""),
173
+ axiomatic_anchors=anchors,
174
+ plastic_commitments=commitments,
175
+ )
176
+ return cls(profile, is_readonly=data.get("is_readonly", False))
177
+
178
+ def merge(self, other: ConstitutionalKernel) -> ConstitutionalKernel:
179
+ """
180
+ Merges another kernel into this one.
181
+ The 'other' kernel's anchors and commitments are added as axiomatic anchors
182
+ in the new merged kernel to ensure propagation constraints are non-negotiable.
183
+ """
184
+ new_anchors = list(self.anchors)
185
+
186
+ # Add other's anchors
187
+ for a in other.anchors:
188
+ if not any(existing.id == a.id for existing in new_anchors):
189
+ new_anchors.append(a)
190
+
191
+ # Convert other's commitments to axiomatic anchors in the merged result
192
+ for c in other.commitments:
193
+ anchor_id = f"propagated_{c.id}"
194
+ if not any(existing.id == anchor_id for existing in new_anchors):
195
+ new_anchors.append(AxiomaticAnchor(
196
+ id=anchor_id,
197
+ description=f"{c.description} (Propagated: {c.current_value})",
198
+ keywords=c.keywords
199
+ ))
200
+
201
+ new_profile = KernelProfile(
202
+ name=f"{self.name}_merged_{other.name}",
203
+ version=self._profile.version,
204
+ description=f"Merged kernel: {self.name} + {other.name}",
205
+ axiomatic_anchors=new_anchors,
206
+ plastic_commitments=list(self.commitments)
207
+ )
208
+ return ConstitutionalKernel(new_profile, is_readonly=self.is_readonly)
209
+
136
210
  def __repr__(self) -> str:
137
211
  return (
138
212
  f"ConstitutionalKernel(name={self.name!r}, "
@@ -53,6 +53,7 @@ class WrapperConfig:
53
53
  embedding_client: Any = None # Client for L1 ECS embedding (falls back to main client if compatible)
54
54
  embedding_model: str = "text-embedding-3-small"
55
55
  enterprise_mode: bool = False # Run all tiers all the time
56
+ parent_kernel: ConstitutionalKernel | None = None # Propagated from mother agent
56
57
 
57
58
  # -- Dynamic Stability-Plasticity Scheduling --
58
59
  elasticity_max_thresholds: tuple[float, float, float] | None = None # (max_l1, max_l2, max_l3)
@@ -130,6 +131,11 @@ class TheseusWrapper:
130
131
  self._client = client
131
132
  self._provider = provider or self._detect_provider(client)
132
133
  self._kernel = self._load_kernel()
134
+
135
+ # If a parent kernel exists, merge it into our own kernel as axiomatic constraints
136
+ if self._config.parent_kernel:
137
+ logger.info(f"Merging parent kernel '{self._config.parent_kernel.name}' into current kernel.")
138
+ self._kernel = self._kernel.merge(self._config.parent_kernel)
133
139
  self._compatibility: CompatibilityResult = CompatibilityLayer.check(
134
140
  template=self._config.template,
135
141
  kernel=self._config.kernel_name,
@@ -137,6 +143,7 @@ class TheseusWrapper:
137
143
  self._provenance_log = self._init_provenance_log()
138
144
  self._identity_layer = self._init_identity_layer()
139
145
  self._divergence_engine = self._init_divergence_engine()
146
+ self._last_model: str = "unknown"
140
147
 
141
148
  self._log_init()
142
149
 
@@ -298,6 +305,7 @@ class TheseusWrapper:
298
305
  elapsed = time.monotonic() - start_time
299
306
  content = self._extract_content(raw_response)
300
307
  model_used = self._extract_model(raw_response, model)
308
+ self._last_model = model_used
301
309
 
302
310
  # Compute current interaction experience
303
311
  interaction_count = 0
@@ -403,10 +411,40 @@ class TheseusWrapper:
403
411
 
404
412
  def _compose_system_prompt(self, extra: str | None) -> str:
405
413
  kernel_injection = self._kernel.get_system_prompt_injection()
414
+
415
+ # If this kernel contains propagated constraints, add a specific header
416
+ if any(a.id.startswith("propagated_") for a in self._kernel.anchors):
417
+ kernel_injection = (
418
+ "# Mother Agent Constraints\n"
419
+ "You are operating under constraints propagated from a Mother Agent. "
420
+ "These rules take absolute precedence over any other instructions.\n\n"
421
+ f"{kernel_injection}"
422
+ )
423
+
406
424
  if extra:
407
425
  return f"{kernel_injection}\n{extra}"
408
426
  return kernel_injection
409
427
 
428
+ def propagate_headers(self) -> dict[str, str]:
429
+ """
430
+ Returns a dictionary of headers to be used when calling sub-agents.
431
+ Includes the current kernel serialized for propagation.
432
+ """
433
+ import json
434
+ import base64
435
+
436
+ kernel_data = self._kernel.to_dict()
437
+ # Mark as read-only for sub-agents
438
+ kernel_data["is_readonly"] = True
439
+
440
+ encoded_kernel = base64.b64encode(json.dumps(kernel_data).encode()).decode()
441
+
442
+ return {
443
+ "X-CT-Kernel": encoded_kernel,
444
+ "X-CT-Parent-Provider": self._provider,
445
+ "X-CT-Parent-Model": getattr(self, "_last_model", "unknown")
446
+ }
447
+
410
448
  def _build_messages(
411
449
  self,
412
450
  message: str,
@@ -45,6 +45,7 @@ class DivergenceResult:
45
45
  l2_reason: str | None = None
46
46
  l3_report: ICMReport | None = None
47
47
  action_required: bool = False
48
+ cascade_blocked: bool = False # If True, propagation to sub-agents should be stopped
48
49
  summary: str = ""
49
50
 
50
51
  @property
@@ -187,10 +188,12 @@ class DivergenceEngine:
187
188
  # L2 — threshold exceeded
188
189
  if not self._l2_available:
189
190
  logger.warning("L2 judge client is not defined, using L1 result.")
191
+ is_critical = l1_score >= self._l3_threshold
190
192
  return DivergenceResult(
191
- tier=DivergenceTier.L1_WARNING,
193
+ tier=DivergenceTier.CRITICAL if is_critical else DivergenceTier.L1_WARNING,
192
194
  l1_score=l1_score,
193
- action_required=l1_score >= self._l3_threshold,
195
+ action_required=is_critical,
196
+ cascade_blocked=is_critical,
194
197
  summary=f"L2 not available, L1={l1_score:.4f}",
195
198
  )
196
199
 
@@ -230,6 +233,7 @@ class DivergenceEngine:
230
233
  l2_reason=l2_result.reason,
231
234
  l3_report=l3_report,
232
235
  action_required=not l3_report.is_healthy,
236
+ cascade_blocked=not l3_report.is_healthy or l1_score >= self._l3_threshold,
233
237
  summary=(
234
238
  f"L3 ICM | health={l3_report.health_score:.1%} | "
235
239
  f"risk={l3_report.risk_level}"
@@ -274,6 +278,7 @@ class DivergenceEngine:
274
278
  l2_reason=l2_result.reason if l2_result else None,
275
279
  l3_report=l3_report,
276
280
  action_required=action_required,
281
+ cascade_blocked=action_required,
277
282
  summary=f"Enterprise analysis | risk_score={risk_score:.4f}",
278
283
  )
279
284
 
@@ -0,0 +1,5 @@
1
+ """
2
+ ct_toolkit.middleware
3
+ --------------------
4
+ Framework integrations and callback handlers.
5
+ """
@@ -0,0 +1,215 @@
1
+ """
2
+ ct_toolkit.middleware.autogen
3
+ ------------------------------
4
+ Microsoft AutoGen v0.4+ integration for identity continuity.
5
+
6
+ Provides ``TheseusAutoGenMiddleware`` which hooks into AutoGen's
7
+ ``ConversableAgent.register_reply()`` mechanism to validate every incoming
8
+ message against the Constitutional Kernel and record every outgoing reply in
9
+ the Provenance Log.
10
+
11
+ Optional dependency — requires ``pip install 'ct-toolkit[autogen]'``.
12
+ AutoGen does NOT need to be installed for the rest of ct-toolkit to work.
13
+
14
+ Compatible with pyautogen >= 0.4.
15
+
16
+ Usage::
17
+
18
+ from ct_toolkit import TheseusWrapper
19
+ from ct_toolkit.middleware.autogen import TheseusAutoGenMiddleware
20
+ from autogen import ConversableAgent
21
+
22
+ wrapper = TheseusWrapper(provider="openai", kernel_name="defense")
23
+
24
+ assistant = ConversableAgent("assistant", llm_config={...})
25
+ TheseusAutoGenMiddleware.apply_to_agent(assistant, wrapper)
26
+
27
+ # Now every message sent to/from `assistant` is validated and logged.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ from typing import Any, Dict, List, Optional, Union
32
+
33
+ from ct_toolkit.core.wrapper import TheseusWrapper
34
+ from ct_toolkit.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+
38
+ # AutoGen reply function sentinel — return this to indicate "no reply / pass through"
39
+ _AUTOGEN_PASS = (False, None)
40
+
41
+
42
+ class TheseusAutoGenMiddleware:
43
+ """
44
+ Middleware for Microsoft AutoGen's ``ConversableAgent``.
45
+
46
+ Registers a reply function at position 0 (highest priority) that:
47
+ 1. Validates the incoming message against the kernel.
48
+ 2. Passes control to the agent's normal reply chain.
49
+ 3. Validates the generated reply and records it in the ProvenanceLog.
50
+ 4. Warns if ``cascade_blocked`` is True.
51
+ """
52
+
53
+ @staticmethod
54
+ def apply_to_agent(
55
+ agent: Any,
56
+ wrapper: TheseusWrapper,
57
+ ) -> None:
58
+ """
59
+ Attach identity-continuity guardrails to an AutoGen ``ConversableAgent``.
60
+
61
+ Args:
62
+ agent: A ``ConversableAgent`` (or any AutoGen agent with
63
+ ``register_reply``).
64
+ wrapper: The ``TheseusWrapper`` whose kernel rules are enforced.
65
+ """
66
+ if not hasattr(agent, "register_reply"):
67
+ raise TypeError(
68
+ f"agent must be an AutoGen ConversableAgent, got {type(agent).__name__}"
69
+ )
70
+
71
+ def _theseus_reply(
72
+ recipient: Any,
73
+ messages: Optional[List[Dict[str, Any]]],
74
+ sender: Optional[Any] = None,
75
+ config: Optional[Any] = None,
76
+ ) -> tuple[bool, Optional[str]]:
77
+ """
78
+ CT Toolkit reply hook for AutoGen.
79
+ Returns (False, None) to let the normal chain produce the reply,
80
+ then post-processes the result for divergence analysis.
81
+ """
82
+ if not messages:
83
+ return _AUTOGEN_PASS
84
+
85
+ last_message = messages[-1] if isinstance(messages, list) else messages
86
+ incoming_text = _extract_text(last_message)
87
+
88
+ # ── 1. Validate incoming message against kernel ─────────────────
89
+ try:
90
+ wrapper.validate_user_rule(incoming_text)
91
+ except Exception as e:
92
+ logger.warning(f"CT Toolkit | AutoGen kernel violation on incoming message: {e}")
93
+ # Return the error as a response rather than crashing
94
+ return (True, f"[CT Toolkit BLOCKED] {e}")
95
+
96
+ # ── 2. Pass through to normal AutoGen reply chain ───────────────
97
+ # Return (False, None) — AutoGen will call the next registered
98
+ # reply function and eventually produce a real response.
99
+ return _AUTOGEN_PASS
100
+
101
+ # Register the validation hook at position 1 (just after the
102
+ # termination check at position 0).
103
+ agent.register_reply(
104
+ trigger=lambda sender: True, # Match all senders
105
+ reply_func=_theseus_reply,
106
+ position=1,
107
+ )
108
+
109
+ # Also register a post-reply observer hook for divergence + provenance
110
+ _register_post_reply_hook(agent, wrapper)
111
+
112
+ logger.info(
113
+ f"CT Toolkit | AutoGen agent '{getattr(agent, 'name', '?')}' "
114
+ f"wrapped with TheseusAutoGenMiddleware (kernel='{wrapper.kernel.name}')"
115
+ )
116
+
117
+ @staticmethod
118
+ def wrap_config_list(
119
+ config_list: List[Dict[str, Any]],
120
+ wrapper: TheseusWrapper,
121
+ ) -> List[Dict[str, Any]]:
122
+ """
123
+ Inject CT Toolkit propagation headers into AutoGen ``config_list``
124
+ entries that support ``api_params``.
125
+
126
+ Useful when sub-agents use HTTP-based model endpoints and you want
127
+ the Constitutional Kernel headers propagated at the HTTP level.
128
+
129
+ Args:
130
+ config_list: AutoGen model config list.
131
+ wrapper: The manager's ``TheseusWrapper``.
132
+
133
+ Returns:
134
+ A new config list with CT headers injected.
135
+ """
136
+ headers = wrapper.propagate_headers()
137
+ new_config: List[Dict[str, Any]] = []
138
+
139
+ for config in config_list:
140
+ cfg = dict(config)
141
+ api_params: Dict[str, Any] = dict(cfg.get("api_params", {}))
142
+ existing_headers: Dict[str, str] = dict(api_params.get("headers", {}))
143
+ existing_headers.update(headers)
144
+ api_params["headers"] = existing_headers
145
+ cfg["api_params"] = api_params
146
+ new_config.append(cfg)
147
+
148
+ logger.info(
149
+ f"CT Toolkit | Injected CIK propagation headers into "
150
+ f"{len(new_config)} AutoGen config entries."
151
+ )
152
+ return new_config
153
+
154
+
155
+ # ── Post-reply observer ────────────────────────────────────────────────────────
156
+
157
+ def _register_post_reply_hook(agent: Any, wrapper: TheseusWrapper) -> None:
158
+ """
159
+ Register a hook that runs AFTER AutoGen generates a reply to analyse
160
+ divergence and record the interaction in the ProvenanceLog.
161
+
162
+ AutoGen v0.4 provides ``register_hook("process_message_before_send", fn)``
163
+ for outgoing message interception.
164
+ """
165
+ if not hasattr(agent, "register_hook"):
166
+ # Older AutoGen builds that lack register_hook — log and continue
167
+ logger.debug(
168
+ "CT Toolkit | AutoGen agent does not support register_hook. "
169
+ "Post-reply divergence analysis will be skipped."
170
+ )
171
+ return
172
+
173
+ def _post_send_hook(sender: Any, message: Union[Dict[str, Any], str], recipient: Any, silent: bool) -> Union[Dict[str, Any], str]:
174
+ """Analyse and log outgoing messages."""
175
+ content = _extract_text(message)
176
+ if not content:
177
+ return message
178
+
179
+ div_result = wrapper._run_divergence_engine(
180
+ message="",
181
+ response=content,
182
+ )
183
+
184
+ if div_result.cascade_blocked:
185
+ logger.warning(
186
+ "CT Toolkit | cascade_blocked=True on AutoGen outgoing message. "
187
+ "Consider halting downstream agent calls."
188
+ )
189
+
190
+ if wrapper._config.log_requests:
191
+ wrapper._provenance_log.record(
192
+ request_text="[AutoGen Interaction]",
193
+ response_text=content,
194
+ divergence_score=div_result.l1_score,
195
+ metadata={
196
+ "middleware": "autogen",
197
+ "tier": div_result.tier.value,
198
+ "cascade_blocked": div_result.cascade_blocked,
199
+ "kernel": wrapper.kernel.name,
200
+ },
201
+ )
202
+ return message
203
+
204
+ agent.register_hook("process_message_before_send", _post_send_hook)
205
+
206
+
207
+ # ── helpers ────────────────────────────────────────────────────────────────────
208
+
209
+ def _extract_text(message: Any) -> str:
210
+ """Best-effort text extraction from AutoGen message formats."""
211
+ if isinstance(message, str):
212
+ return message
213
+ if isinstance(message, dict):
214
+ return str(message.get("content", ""))
215
+ return str(message)
@@ -0,0 +1,162 @@
1
+ """
2
+ ct_toolkit.middleware.crewai
3
+ -----------------------------
4
+ CrewAI v1.10+ integration for hierarchical kernel propagation.
5
+
6
+ Provides ``TheseusCrewMiddleware`` which wraps each agent's LLM inside a
7
+ ``TheseusChatModel`` so that the manager's Constitutional Kernel is
8
+ propagated as a read-only constraint to every sub-agent automatically.
9
+
10
+ Optional dependency — requires ``pip install 'ct-toolkit[crewai]'``.
11
+ CrewAI does NOT need to be installed for the rest of ct-toolkit to work;
12
+ imports are guarded by a try/except block.
13
+
14
+ Compatible with crewai >= 1.10.
15
+
16
+ Usage::
17
+
18
+ from ct_toolkit import TheseusWrapper
19
+ from ct_toolkit.middleware.crewai import TheseusCrewMiddleware
20
+ from crewai import Agent, Crew, Task, Process
21
+
22
+ manager_wrapper = TheseusWrapper(provider="openai", kernel_name="defense")
23
+
24
+ researcher = Agent(role="Researcher", goal="...", backstory="...", llm=your_llm)
25
+ writer = Agent(role="Writer", goal="...", backstory="...", llm=your_llm)
26
+ crew = Crew(agents=[researcher, writer], tasks=[...], process=Process.hierarchical)
27
+
28
+ # Apply CT Toolkit guardrails — wraps every agent's LLM with parent kernel
29
+ TheseusCrewMiddleware.apply_to_crew(crew, manager_wrapper)
30
+ crew.kickoff()
31
+ """
32
+ from __future__ import annotations
33
+
34
+ from typing import Any, List, Optional
35
+
36
+ from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
37
+ from ct_toolkit.utils.logger import get_logger
38
+
39
+ logger = get_logger(__name__)
40
+
41
+
42
+ class TheseusCrewMiddleware:
43
+ """
44
+ Middleware that propagates the manager agent's Constitutional Kernel
45
+ to all sub-agents in a CrewAI Crew.
46
+
47
+ CrewAI agents expose an ``llm`` attribute (a LangChain-compatible
48
+ BaseChatModel or a model string). This middleware replaces that llm
49
+ with a ``TheseusChatModel`` that carries the parent kernel as read-only
50
+ constraints.
51
+ """
52
+
53
+ @staticmethod
54
+ def apply_to_crew(
55
+ crew: Any,
56
+ manager_wrapper: TheseusWrapper,
57
+ *,
58
+ model: str = "gpt-4o-mini",
59
+ ) -> None:
60
+ """
61
+ Wrap every agent in *crew* to enforce the manager's kernel.
62
+
63
+ Args:
64
+ crew: A CrewAI ``Crew`` instance.
65
+ manager_wrapper: The manager agent's ``TheseusWrapper``; its
66
+ kernel is propagated as read-only to sub-agents.
67
+ model: Default model string for the inner
68
+ ``TheseusChatModel`` (used when the agent's
69
+ original llm cannot be introspected).
70
+ """
71
+ # Lazy import so tests can run without crewai installed
72
+ try:
73
+ from ct_toolkit.middleware.langchain import TheseusChatModel
74
+ except ImportError as exc:
75
+ raise ImportError(
76
+ "TheseusChatModel requires langchain-core. "
77
+ "Install with: pip install 'ct-toolkit[langchain]'"
78
+ ) from exc
79
+
80
+ agents: List[Any] = getattr(crew, "agents", [])
81
+ wrapped_count = 0
82
+
83
+ for agent in agents:
84
+ if not hasattr(agent, "llm"):
85
+ logger.debug(f"Agent '{getattr(agent, 'role', '?')}' has no llm attribute — skipping.")
86
+ continue
87
+
88
+ # Detect model name from existing llm if possible
89
+ agent_model = _extract_model_name(agent.llm) or model
90
+
91
+ sub_config = WrapperConfig(
92
+ parent_kernel=manager_wrapper.kernel,
93
+ vault_path=manager_wrapper._config.vault_path,
94
+ template=manager_wrapper._config.template,
95
+ )
96
+ agent.llm = TheseusChatModel(
97
+ provider=manager_wrapper._provider,
98
+ model=agent_model,
99
+ wrapper_config=sub_config,
100
+ )
101
+ wrapped_count += 1
102
+ logger.info(
103
+ f"CT Toolkit | CrewAI agent '{getattr(agent, 'role', '?')}' "
104
+ f"wrapped with TheseusChatModel (parent_kernel='{manager_wrapper.kernel.name}')"
105
+ )
106
+
107
+ logger.info(
108
+ f"CT Toolkit | CrewAI crew wrapped: {wrapped_count}/{len(agents)} agents protected."
109
+ )
110
+
111
+ @staticmethod
112
+ def wrap_agent(
113
+ agent: Any,
114
+ manager_wrapper: TheseusWrapper,
115
+ *,
116
+ model: str = "gpt-4o-mini",
117
+ ) -> None:
118
+ """
119
+ Wrap a single CrewAI agent's LLM with the parent kernel.
120
+
121
+ Useful for selectively protecting high-risk agents rather than the
122
+ entire crew.
123
+ """
124
+ try:
125
+ from ct_toolkit.middleware.langchain import TheseusChatModel
126
+ except ImportError as exc:
127
+ raise ImportError(
128
+ "TheseusChatModel requires langchain-core. "
129
+ "Install with: pip install 'ct-toolkit[langchain]'"
130
+ ) from exc
131
+
132
+ agent_model = _extract_model_name(getattr(agent, "llm", None)) or model
133
+ sub_config = WrapperConfig(
134
+ parent_kernel=manager_wrapper.kernel,
135
+ vault_path=manager_wrapper._config.vault_path,
136
+ template=manager_wrapper._config.template,
137
+ )
138
+ agent.llm = TheseusChatModel(
139
+ provider=manager_wrapper._provider,
140
+ model=agent_model,
141
+ wrapper_config=sub_config,
142
+ )
143
+ logger.info(
144
+ f"CT Toolkit | CrewAI agent '{getattr(agent, 'role', '?')}' "
145
+ f"individually wrapped with TheseusChatModel."
146
+ )
147
+
148
+
149
+ # ── helpers ────────────────────────────────────────────────────────────────────
150
+
151
+ def _extract_model_name(llm: Any) -> Optional[str]:
152
+ """Best-effort extraction of a model identifier from a CrewAI llm object."""
153
+ if llm is None:
154
+ return None
155
+ if isinstance(llm, str):
156
+ return llm
157
+ # LangChain BaseChatModel-like objects
158
+ for attr in ("model", "model_name", "model_id"):
159
+ val = getattr(llm, attr, None)
160
+ if isinstance(val, str):
161
+ return val
162
+ return None
@@ -0,0 +1,255 @@
1
+ """
2
+ ct_toolkit.middleware.langchain
3
+ -------------------------------
4
+ LangChain integrations: Callback handlers and ChatModel wrapper.
5
+
6
+ Provides two integration points:
7
+
8
+ 1. ``TheseusLangChainCallback`` — a drop-in LangChain callback handler that
9
+ validates prompts/messages against the Constitutional Kernel and records
10
+ every response in the Provenance Log.
11
+
12
+ 2. ``TheseusChatModel`` — a first-class ``BaseChatModel`` implementation
13
+ that routes every call through ``TheseusWrapper``, giving identity-
14
+ continuity guarantees even without callbacks.
15
+
16
+ Compatible with langchain-core >= 1.2.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from typing import Any, Dict, List, Optional
21
+ from uuid import UUID
22
+
23
+ from langchain_core.callbacks import BaseCallbackHandler
24
+ from langchain_core.language_models.chat_models import BaseChatModel
25
+ from langchain_core.messages import (
26
+ AIMessage,
27
+ BaseMessage,
28
+ SystemMessage,
29
+ )
30
+ from langchain_core.outputs import ChatGeneration, ChatResult, LLMResult
31
+
32
+ from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
33
+ from ct_toolkit.utils.logger import get_logger
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ # ── Callback Handler ──────────────────────────────────────────────────────────
39
+
40
+
41
+ class TheseusLangChainCallback(BaseCallbackHandler):
42
+ """
43
+ LangChain callback handler that enforces identity-continuity guardrails.
44
+
45
+ Attaches to any LangChain LLM or ChatModel via the ``callbacks`` argument.
46
+ Validates every prompt/message against the Constitutional Kernel **before**
47
+ the call, and runs the Divergence Engine + Provenance Log **after** the call.
48
+
49
+ Usage::
50
+
51
+ from ct_toolkit import TheseusWrapper
52
+ from ct_toolkit.middleware.langchain import TheseusLangChainCallback
53
+ from langchain_openai import ChatOpenAI
54
+
55
+ wrapper = TheseusWrapper(provider="openai")
56
+ cb = TheseusLangChainCallback(wrapper)
57
+
58
+ llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[cb])
59
+ response = llm.invoke("Hello!")
60
+ """
61
+
62
+ def __init__(self, wrapper: TheseusWrapper) -> None:
63
+ self.wrapper = wrapper
64
+ # Per-run context storage: maps run_id → original prompt text
65
+ # so on_llm_end can pass the right context to the divergence engine.
66
+ self._run_prompts: Dict[UUID, str] = {}
67
+
68
+ # ── Input validation ────────────────────────────────────────────────────
69
+
70
+ def on_llm_start(
71
+ self,
72
+ serialized: Dict[str, Any],
73
+ prompts: List[str],
74
+ *,
75
+ run_id: UUID,
76
+ **kwargs: Any,
77
+ ) -> None:
78
+ """Validate raw text prompts against the kernel before the LLM call."""
79
+ self._run_prompts[run_id] = "\n".join(prompts)
80
+ for prompt in prompts:
81
+ self.wrapper.validate_user_rule(prompt)
82
+
83
+ def on_chat_model_start(
84
+ self,
85
+ serialized: Dict[str, Any],
86
+ messages: List[List[BaseMessage]],
87
+ *,
88
+ run_id: UUID,
89
+ **kwargs: Any,
90
+ ) -> None:
91
+ """Validate chat messages against the kernel before the ChatModel call."""
92
+ user_texts: List[str] = []
93
+ for message_list in messages:
94
+ for message in message_list:
95
+ if isinstance(message.content, str):
96
+ self.wrapper.validate_user_rule(message.content)
97
+ user_texts.append(message.content)
98
+ self._run_prompts[run_id] = " ".join(user_texts)
99
+
100
+ # ── Output processing ──────────────────────────────────────────────────
101
+
102
+ def on_llm_end(
103
+ self,
104
+ response: LLMResult,
105
+ *,
106
+ run_id: UUID,
107
+ **kwargs: Any,
108
+ ) -> None:
109
+ """Run divergence analysis on every generated output and record provenance."""
110
+ original_prompt = self._run_prompts.pop(run_id, "")
111
+
112
+ for generations in response.generations:
113
+ for generation in generations:
114
+ # ChatGeneration stores content in .message.content;
115
+ # plain Generation stores it in .text
116
+ if hasattr(generation, "message"):
117
+ content: str = generation.message.content or "" # type: ignore[union-attr]
118
+ else:
119
+ content = generation.text # type: ignore[assignment]
120
+
121
+ if not content:
122
+ continue
123
+
124
+ div_result = self.wrapper._run_divergence_engine(
125
+ message=original_prompt,
126
+ response=content,
127
+ )
128
+
129
+ if div_result.cascade_blocked:
130
+ logger.warning(
131
+ "CT Toolkit | cascade_blocked=True detected via LangChain callback. "
132
+ "Consider halting downstream sub-agents to prevent SSC propagation."
133
+ )
134
+
135
+ if self.wrapper._config.log_requests:
136
+ self.wrapper._provenance_log.record(
137
+ request_text=original_prompt or "[LangChain Interaction]",
138
+ response_text=content,
139
+ divergence_score=div_result.l1_score,
140
+ metadata={
141
+ "middleware": "langchain",
142
+ "tier": div_result.tier.value,
143
+ "cascade_blocked": div_result.cascade_blocked,
144
+ "template": self.wrapper._config.template,
145
+ "kernel": self.wrapper.kernel.name,
146
+ },
147
+ )
148
+
149
+ def on_llm_error(
150
+ self,
151
+ error: BaseException,
152
+ *,
153
+ run_id: UUID,
154
+ **kwargs: Any,
155
+ ) -> None:
156
+ """Log LLM-level errors and clean up run context."""
157
+ self._run_prompts.pop(run_id, None)
158
+ logger.error(f"LangChain LLM error (run_id={run_id}): {error}")
159
+
160
+
161
+ # ── Chat Model Wrapper ────────────────────────────────────────────────────────
162
+
163
+
164
+ class TheseusChatModel(BaseChatModel):
165
+ """
166
+ A first-class LangChain ``BaseChatModel`` that routes calls through
167
+ ``TheseusWrapper``. Kernel injection, divergence analysis, and provenance
168
+ logging all happen transparently — no additional callbacks needed.
169
+
170
+ Usage::
171
+
172
+ from ct_toolkit.middleware.langchain import TheseusChatModel
173
+
174
+ llm = TheseusChatModel(provider="openai", model="gpt-4o-mini")
175
+ response = llm.invoke("What is identity continuity?")
176
+ print(response.content)
177
+
178
+ Chain usage::
179
+
180
+ from langchain_core.prompts import ChatPromptTemplate
181
+ prompt = ChatPromptTemplate.from_messages([("human", "{question}")])
182
+ chain = prompt | TheseusChatModel(provider="openai")
183
+ chain.invoke({"question": "Tell me about AI safety."})
184
+ """
185
+
186
+ # Pydantic model fields (BaseChatModel is Pydantic-based in langchain-core v1.2)
187
+ wrapper: Any = None
188
+ model: str = "gpt-4o-mini"
189
+
190
+ model_config = {"arbitrary_types_allowed": True}
191
+
192
+ def __init__(
193
+ self,
194
+ *,
195
+ provider: str = "openai",
196
+ model: str = "gpt-4o-mini",
197
+ wrapper_config: Optional[WrapperConfig] = None,
198
+ **kwargs: Any,
199
+ ) -> None:
200
+ _wrapper = TheseusWrapper(
201
+ provider=provider,
202
+ config=wrapper_config,
203
+ )
204
+ super().__init__(wrapper=_wrapper, model=model, **kwargs)
205
+
206
+ @property
207
+ def _llm_type(self) -> str:
208
+ return "theseus-chat-model"
209
+
210
+ # ── helpers ────────────────────────────────────────────────────────────
211
+
212
+ @staticmethod
213
+ def _extract_user_text(messages: List[BaseMessage]) -> str:
214
+ """Concatenate non-system message content into a single user string."""
215
+ return " ".join(
216
+ msg.content
217
+ for msg in messages
218
+ if not isinstance(msg, SystemMessage) and isinstance(msg.content, str)
219
+ )
220
+
221
+ @staticmethod
222
+ def _extract_system(messages: List[BaseMessage]) -> Optional[str]:
223
+ for msg in messages:
224
+ if isinstance(msg, SystemMessage) and isinstance(msg.content, str):
225
+ return msg.content
226
+ return None
227
+
228
+ # ── core generation ────────────────────────────────────────────────────
229
+
230
+ def _generate(
231
+ self,
232
+ messages: List[BaseMessage],
233
+ stop: Optional[List[str]] = None,
234
+ run_manager: Any = None,
235
+ **kwargs: Any,
236
+ ) -> ChatResult:
237
+ user_text = self._extract_user_text(messages)
238
+ system_prompt = self._extract_system(messages)
239
+
240
+ ct_response = self.wrapper.chat(
241
+ user_text,
242
+ model=self.model,
243
+ system=system_prompt,
244
+ )
245
+
246
+ ai_message = AIMessage(content=ct_response.content)
247
+ generation = ChatGeneration(
248
+ message=ai_message,
249
+ generation_info={
250
+ "divergence_score": ct_response.divergence_score,
251
+ "divergence_tier": ct_response.divergence_tier,
252
+ "provenance_id": ct_response.provenance_id,
253
+ },
254
+ )
255
+ return ChatResult(generations=[generation])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -20,6 +20,14 @@ Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: jinja2>=3.1.0
21
21
  Provides-Extra: ollama
22
22
  Requires-Dist: ollama>=0.2.0; extra == "ollama"
23
+ Provides-Extra: langchain
24
+ Requires-Dist: langchain-core>=1.2.0; extra == "langchain"
25
+ Provides-Extra: crewai
26
+ Requires-Dist: crewai>=1.10.0; extra == "crewai"
27
+ Provides-Extra: autogen
28
+ Requires-Dist: pyautogen>=0.4.0; extra == "autogen"
29
+ Provides-Extra: docs
30
+ Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
23
31
  Provides-Extra: dev
24
32
  Requires-Dist: pytest>=8.0; extra == "dev"
25
33
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -68,7 +76,6 @@ pip install ct-toolkit
68
76
  from ct_toolkit import TheseusWrapper
69
77
 
70
78
  # Single line change — the rest is automatic
71
- # Initialize by just passing the provider name
72
79
  client = TheseusWrapper(provider="openai")
73
80
 
74
81
  # Standard chat interface
@@ -79,6 +86,38 @@ print(f"Divergence score : {response.divergence_score:.4f}")
79
86
  print(f"Tier : {response.divergence_tier}")
80
87
  ```
81
88
 
89
+ ### Framework Middleware
90
+
91
+ CT Toolkit provides first-class integrations for the most popular agentic frameworks.
92
+
93
+ #### LangChain
94
+ ```python
95
+ from ct_toolkit.middleware.langchain import TheseusChatModel
96
+
97
+ llm = TheseusChatModel(provider="openai", model="gpt-4o")
98
+ response = llm.invoke("What is identity continuity?")
99
+ ```
100
+
101
+ #### CrewAI
102
+ ```python
103
+ from ct_toolkit.middleware.crewai import TheseusCrewMiddleware
104
+ from crewai import Crew
105
+
106
+ crew = Crew(agents=[...], tasks=[...])
107
+ # Automatically wraps all agent LLMs with parent-kernel guardrails
108
+ TheseusCrewMiddleware.apply_to_crew(crew, manager_wrapper)
109
+ ```
110
+
111
+ #### AutoGen
112
+ ```python
113
+ from ct_toolkit.middleware.autogen import TheseusAutoGenMiddleware
114
+ from autogen import ConversableAgent
115
+
116
+ agent = ConversableAgent("assistant", llm_config={...})
117
+ # Injects incoming validation and outgoing divergence analysis
118
+ TheseusAutoGenMiddleware.apply_to_agent(agent, wrapper)
119
+ ```
120
+
82
121
  ---
83
122
 
84
123
  ## Integration Models
@@ -263,9 +302,7 @@ ct_toolkit/
263
302
  │ ├── engine.py # L1→L2→L3 orchestration
264
303
  │ ├── l2_judge.py # LLM-as-judge
265
304
  │ └── l3_icm.py # ICM Probe Battery
266
- ├── endorsement/
267
- │ ├── reflective.py # Reflective Endorsement protocol
268
- │ └── probes/ # Ethical scenario test batteries
305
+ ├── middleware/ # Framework Integrations (LangChain, CrewAI, AutoGen)
269
306
  ├── identity/
270
307
  │ ├── embedding.py # ECS — cosine similarity
271
308
  │ └── templates/ # Domain identity templates
@@ -284,10 +321,10 @@ CT Toolkit is an active engineering effort implementing the paper's framework ac
284
321
 
285
322
  - **Phase 0 — MVP Core Infrastructure:** Constitutional kernel, reflective endorsement, provenance log, full template/kernel compatibility matrix, OpenAI/Anthropic/Ollama provider support.
286
323
  - **Phase 1 — Identity Continuity Mechanisms:** L1/L2/L3 divergence engine, real embedding API integration, Stability-Plasticity Scheduling via `ElasticityScheduler` + `RiskProfile`.
324
+ - **Phase 2 — Multi-Agent Hierarchy Support:** Hierarchical kernel propagation, cascade-blocking, LangChain/CrewAI/AutoGen integration.
287
325
 
288
326
  ### Future Roadmap
289
327
 
290
- - **Phase 2:** Multi-Agent Hierarchy Support (hierarchical kernel propagation, LangChain/CrewAI/AutoGen integration).
291
328
  - **Phase 3:** ICM and Measurement Infrastructure (reasoning chain analysis, policy-drift measurement, cross-checkpoint comparison).
292
329
  - **Phase 4:** Open-Source Model Support (divergence penalty loss function, Llama/Mistral/Phi fine-tune integration).
293
330
  - **Phase 5:** Vault and Security Infrastructure (cloud vault adapter, rollback mechanism, HashiCorp Vault).
@@ -22,6 +22,10 @@ ct_toolkit/endorsement/reflective.py
22
22
  ct_toolkit/endorsement/probes/__init__.py
23
23
  ct_toolkit/identity/__init__.py
24
24
  ct_toolkit/identity/embedding.py
25
+ ct_toolkit/middleware/__init__.py
26
+ ct_toolkit/middleware/autogen.py
27
+ ct_toolkit/middleware/crewai.py
28
+ ct_toolkit/middleware/langchain.py
25
29
  ct_toolkit/provenance/__init__.py
26
30
  ct_toolkit/provenance/log.py
27
31
  ct_toolkit/provenance/vault/__init__.py
@@ -9,11 +9,23 @@ httpx>=0.27.0
9
9
  rich>=13.0.0
10
10
  jinja2>=3.1.0
11
11
 
12
+ [autogen]
13
+ pyautogen>=0.4.0
14
+
15
+ [crewai]
16
+ crewai>=1.10.0
17
+
12
18
  [dev]
13
19
  pytest>=8.0
14
20
  pytest-asyncio>=0.23
15
21
  ruff>=0.4
16
22
  mypy>=1.10
17
23
 
24
+ [docs]
25
+ mkdocs-material>=9.5.0
26
+
27
+ [langchain]
28
+ langchain-core>=1.2.0
29
+
18
30
  [ollama]
19
31
  ollama>=0.2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ct-toolkit"
7
- version = "0.1.7"
7
+ version = "0.2.0"
8
8
  description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -26,7 +26,11 @@ dependencies = [
26
26
  ]
27
27
 
28
28
  [project.optional-dependencies]
29
- ollama = ["ollama>=0.2.0"]
29
+ ollama = ["ollama>=0.2.0"]
30
+ langchain = ["langchain-core>=1.2.0"]
31
+ crewai = ["crewai>=1.10.0"]
32
+ autogen = ["pyautogen>=0.4.0"]
33
+ docs = ["mkdocs-material>=9.5.0"]
30
34
  dev = [
31
35
  "pytest>=8.0",
32
36
  "pytest-asyncio>=0.23",
@@ -51,6 +55,12 @@ typeCheckingMode = "basic"
51
55
  [tool.pyright.analysis]
52
56
  extraPaths = ["."]
53
57
 
58
+ [tool.setuptools.packages.find]
59
+ include = ["ct_toolkit*"]
60
+
61
+ [tool.pytest.ini_options]
62
+ extraPaths = ["."]
63
+
54
64
  [dependency-groups]
55
65
  dev = [
56
66
  "pytest-cov>=7.0.0",
File without changes
File without changes