From 10dd4a5a524842e4d52c127d4f6832f0daa9f935 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:26 +0800 Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=93=A6=20(adapters):=20Create=20emp?= =?UTF-8?q?ty=20adapters=20package=20with=20=5F=5Finit=5F=5F.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 agent_assembly/adapters/__init__.py diff --git a/agent_assembly/adapters/__init__.py b/agent_assembly/adapters/__init__.py new file mode 100644 index 0000000..e69de29 From 0b6c59893fab91c8055cbf5bcf2dc0a61964dba2 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:31 +0800 Subject: [PATCH 02/34] =?UTF-8?q?=F0=9F=93=A6=20(adapters):=20Create=20emp?= =?UTF-8?q?ty=20base=20adapter=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 agent_assembly/adapters/base.py diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py new file mode 100644 index 0000000..e69de29 From b5e904d4d7c61f4666462f9512e9be73515e6f7f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:34 +0800 Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=93=A6=20(tests):=20Create=20empty?= =?UTF-8?q?=20adapters=20unit-test=20package=20with=20=5F=5Finit=5F=5F.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/unit/adapters/__init__.py diff --git a/test/unit/adapters/__init__.py b/test/unit/adapters/__init__.py new file mode 100644 index 0000000..e69de29 From 4ffe4a84e59898c3361fefd117923e1b199123a3 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:39 +0800 Subject: [PATCH 04/34] =?UTF-8?q?=F0=9F=93=A6=20(tests):=20Create=20empty?= =?UTF-8?q?=20base=20adapter=20test=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/unit/adapters/test_base.py diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py new file mode 100644 index 0000000..e69de29 From 9d7865595490e77e6206d89864ac47cecfc736c7 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:48 +0800 Subject: [PATCH 05/34] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20(adapters):=20Add?= =?UTF-8?q?=20GovernanceInterceptor=20protocol=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index e69de29..0d5de8d 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from typing import Protocol + + +class GovernanceInterceptor(Protocol): + pass From b8a33ec6e17c11669f01c8b7a4a48b99b3dd0f67 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:54 +0800 Subject: [PATCH 06/34] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20(adapters):=20Add?= =?UTF-8?q?=20FrameworkAdapter=20ABC=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 0d5de8d..4dfad95 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -1,7 +1,12 @@ from __future__ import annotations +from abc import ABC from typing import Protocol class GovernanceInterceptor(Protocol): pass + + +class FrameworkAdapter(ABC): + pass From d80e5af867fc64e38f9987ce6cfb8c7ee0835657 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:19:59 +0800 Subject: [PATCH 07/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20abstract?= =?UTF-8?q?=20method=20get=5Fframework=5Fname()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 4dfad95..3aac408 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -1,6 +1,6 @@ from __future__ import annotations -from abc import ABC +from abc import ABC, abstractmethod from typing import Protocol @@ -9,4 +9,6 @@ class GovernanceInterceptor(Protocol): class FrameworkAdapter(ABC): - pass + @abstractmethod + def get_framework_name(self) -> str: + ... From 6b4a022108116eea640b04b53707b24f0eb17310 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:05 +0800 Subject: [PATCH 08/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20abstract?= =?UTF-8?q?=20method=20get=5Fsupported=5Fversions()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 3aac408..cde9a8c 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -12,3 +12,7 @@ class FrameworkAdapter(ABC): @abstractmethod def get_framework_name(self) -> str: ... + + @abstractmethod + def get_supported_versions(self) -> list[str]: + ... From 367a9eba49cc4c3f1f5eb52bcf5b0fedb766670d Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:11 +0800 Subject: [PATCH 09/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20abstract?= =?UTF-8?q?=20method=20register=5Fhooks()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index cde9a8c..df67e40 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -16,3 +16,7 @@ def get_framework_name(self) -> str: @abstractmethod def get_supported_versions(self) -> list[str]: ... + + @abstractmethod + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + ... From b481d4a46ff2fda51c89169a342e18b68b2df43a Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:16 +0800 Subject: [PATCH 10/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20abstract?= =?UTF-8?q?=20method=20unregister=5Fhooks()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index df67e40..3f7b595 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -20,3 +20,7 @@ def get_supported_versions(self) -> list[str]: @abstractmethod def register_hooks(self, interceptor: GovernanceInterceptor) -> None: ... + + @abstractmethod + def unregister_hooks(self) -> None: + ... From 277de6ef71f7edb73c17e3745768a532baaa8c47 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:22 +0800 Subject: [PATCH 11/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20default?= =?UTF-8?q?=20is=5Favailable()=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 3f7b595..e9eebff 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +import importlib from typing import Protocol @@ -24,3 +25,11 @@ def register_hooks(self, interceptor: GovernanceInterceptor) -> None: @abstractmethod def unregister_hooks(self) -> None: ... + + def is_available(self) -> bool: + try: + importlib.import_module(self.get_framework_name()) + except ImportError: + return False + + return True From 0f93abc89f3750e0a84eab45b887e3a56ce060d5 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:30 +0800 Subject: [PATCH 12/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20default?= =?UTF-8?q?=20get=5Factive=5Fversion()=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index e9eebff..7a0b900 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -33,3 +33,15 @@ def is_available(self) -> bool: return False return True + + def get_active_version(self) -> str | None: + try: + module = importlib.import_module(self.get_framework_name()) + except ImportError: + return None + + version = getattr(module, "__version__", None) + if isinstance(version, str): + return version + + return None From e8f2461d15bc482cb0c270661c56b9dcc82f69a1 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:36 +0800 Subject: [PATCH 13/34] =?UTF-8?q?=E2=9C=A8=20(exceptions):=20Add=20Adapter?= =?UTF-8?q?ValidationError=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/exceptions/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/agent_assembly/exceptions/__init__.py b/agent_assembly/exceptions/__init__.py index 14886a3..b3b6d0c 100644 --- a/agent_assembly/exceptions/__init__.py +++ b/agent_assembly/exceptions/__init__.py @@ -26,3 +26,8 @@ class GatewayError(AssemblyError): class ConfigurationError(AssemblyError): """Exception raised for configuration errors.""" pass + + +class AdapterValidationError(AssemblyError): + """Exception raised when an adapter contract is invalid.""" + pass From ccdb780a0c328776ded805b1b4e6a34a0061c783 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:44 +0800 Subject: [PATCH 14/34] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(exceptions):=20Expo?= =?UTF-8?q?rt=20AdapterValidationError=20from=20exceptions=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/exceptions/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/agent_assembly/exceptions/__init__.py b/agent_assembly/exceptions/__init__.py index b3b6d0c..b973d5d 100644 --- a/agent_assembly/exceptions/__init__.py +++ b/agent_assembly/exceptions/__init__.py @@ -2,6 +2,15 @@ from __future__ import annotations +__all__ = [ + "AssemblyError", + "AgentError", + "PolicyError", + "GatewayError", + "ConfigurationError", + "AdapterValidationError", +] + class AssemblyError(Exception): """Base exception for Agent Assembly SDK errors.""" From bfaed11785da45c5ee3c7e530b1319d7d7c2753b Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:51 +0800 Subject: [PATCH 15/34] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(adapters):=20Export?= =?UTF-8?q?=20GovernanceInterceptor=20in=20adapters=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agent_assembly/adapters/__init__.py b/agent_assembly/adapters/__init__.py index e69de29..9271ee6 100644 --- a/agent_assembly/adapters/__init__.py +++ b/agent_assembly/adapters/__init__.py @@ -0,0 +1,3 @@ +from agent_assembly.adapters.base import GovernanceInterceptor + +__all__ = ["GovernanceInterceptor"] From 0dbc66d5517468992e66265c9606994aa58470e9 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:20:55 +0800 Subject: [PATCH 16/34] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(adapters):=20Export?= =?UTF-8?q?=20FrameworkAdapter=20in=20adapters=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent_assembly/adapters/__init__.py b/agent_assembly/adapters/__init__.py index 9271ee6..c16f448 100644 --- a/agent_assembly/adapters/__init__.py +++ b/agent_assembly/adapters/__init__.py @@ -1,3 +1,3 @@ -from agent_assembly.adapters.base import GovernanceInterceptor +from agent_assembly.adapters.base import FrameworkAdapter, GovernanceInterceptor -__all__ = ["GovernanceInterceptor"] +__all__ = ["GovernanceInterceptor", "FrameworkAdapter"] From f48b249aeff05ea82a40991d00139ca9f7381224 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:02 +0800 Subject: [PATCH 17/34] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(sdk):=20Re-export?= =?UTF-8?q?=20adapter=20base=20types=20from=20root=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/agent_assembly/__init__.py b/agent_assembly/__init__.py index b038b1a..eaf5edd 100644 --- a/agent_assembly/__init__.py +++ b/agent_assembly/__init__.py @@ -1,7 +1,9 @@ """Agent Assembly Python SDK.""" +from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor from agent_assembly.core import init_assembly from agent_assembly.exceptions import ( + AdapterValidationError, AgentError, AssemblyError, ConfigurationError, @@ -14,9 +16,12 @@ __all__ = [ "__version__", "init_assembly", + "GovernanceInterceptor", + "FrameworkAdapter", "AssemblyError", "AgentError", "PolicyError", "GatewayError", "ConfigurationError", -] \ No newline at end of file + "AdapterValidationError", +] From 8e9e755e9e199f3e38900e30c272264654d903af Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:10 +0800 Subject: [PATCH 18/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20GovernanceInterceptor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 7a0b900..cc688e8 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -6,6 +6,8 @@ class GovernanceInterceptor(Protocol): + """Protocol implemented by governance interceptors used by adapters.""" + pass From 57b9c6036f48ea1a3065db0bd850da00678b6873 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:14 +0800 Subject: [PATCH 19/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20FrameworkAdapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index cc688e8..101d087 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -12,6 +12,8 @@ class GovernanceInterceptor(Protocol): class FrameworkAdapter(ABC): + """Abstract contract implemented by every framework adapter.""" + @abstractmethod def get_framework_name(self) -> str: ... From 46a8ca7f7a744bf25a9efcd336b68037aa674112 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:19 +0800 Subject: [PATCH 20/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20get=5Fframework=5Fname()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 101d087..91211e2 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -16,6 +16,8 @@ class FrameworkAdapter(ABC): @abstractmethod def get_framework_name(self) -> str: + """Return the canonical importable framework package name.""" + ... @abstractmethod From 99d6a2c152bd3aeaf7d3c863ddfb8c2610a5ec1a Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:23 +0800 Subject: [PATCH 21/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20get=5Fsupported=5Fversions()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 91211e2..bc72adb 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -22,6 +22,8 @@ def get_framework_name(self) -> str: @abstractmethod def get_supported_versions(self) -> list[str]: + """Return supported semantic version ranges for the framework.""" + ... @abstractmethod From 0c323700f38e019a3e806885bbea817304090334 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:30 +0800 Subject: [PATCH 22/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20register=5Fhooks()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index bc72adb..a55d962 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -28,6 +28,8 @@ def get_supported_versions(self) -> list[str]: @abstractmethod def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + """Attach framework hooks to a governance interceptor instance.""" + ... @abstractmethod From a9c30dfeae66cce2ddf31d9e62f7b5d90e13f76e Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:35 +0800 Subject: [PATCH 23/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20unregister=5Fhooks()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index a55d962..1e20f14 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -34,6 +34,8 @@ def register_hooks(self, interceptor: GovernanceInterceptor) -> None: @abstractmethod def unregister_hooks(self) -> None: + """Detach all framework hooks in an idempotent way.""" + ... def is_available(self) -> bool: From e8cd0b2b8e2a8c6631e475ea1579a40b64c8186f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:39 +0800 Subject: [PATCH 24/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20is=5Favailable()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 1e20f14..a9c76ea 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -39,6 +39,8 @@ def unregister_hooks(self) -> None: ... def is_available(self) -> bool: + """Return True when the framework package can be imported.""" + try: importlib.import_module(self.get_framework_name()) except ImportError: From bf194ccb995ae0d9facd22a67ee72099b7ca375a Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:44 +0800 Subject: [PATCH 25/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Add=20docstr?= =?UTF-8?q?ing=20for=20get=5Factive=5Fversion()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index a9c76ea..8e30139 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -49,6 +49,8 @@ def is_available(self) -> bool: return True def get_active_version(self) -> str | None: + """Return framework __version__ when present, otherwise None.""" + try: module = importlib.import_module(self.get_framework_name()) except ImportError: From 6865d1bd995f05975131dc3c0edac57e76afd1d5 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:21:57 +0800 Subject: [PATCH 26/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20test=20for?= =?UTF-8?q?=20abstract=20contract=20enforcement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index e69de29..edf1b70 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -0,0 +1,13 @@ +import pytest + +from agent_assembly.adapters import FrameworkAdapter + + +class IncompleteAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "math" + + +def test_framework_adapter_requires_all_abstract_methods() -> None: + with pytest.raises(TypeError): + IncompleteAdapter() From 66886244646238dcb2c7f2cd3c1398a34ec35126 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:22:05 +0800 Subject: [PATCH 27/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20test=20for?= =?UTF-8?q?=20is=5Favailable()=20when=20framework=20exists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index edf1b70..2fa32ba 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -1,6 +1,6 @@ import pytest -from agent_assembly.adapters import FrameworkAdapter +from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor class IncompleteAdapter(FrameworkAdapter): @@ -11,3 +11,21 @@ def get_framework_name(self) -> str: def test_framework_adapter_requires_all_abstract_methods() -> None: with pytest.raises(TypeError): IncompleteAdapter() + + +class AvailableFrameworkAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "math" + + def get_supported_versions(self) -> list[str]: + return [] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_is_available_returns_true_when_framework_exists() -> None: + assert AvailableFrameworkAdapter().is_available() is True From e945d1140e701dac0b7b509b72e59e930117783f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:22:11 +0800 Subject: [PATCH 28/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20test=20for?= =?UTF-8?q?=20is=5Favailable()=20when=20framework=20missing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index 2fa32ba..ef38c0a 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -29,3 +29,21 @@ def unregister_hooks(self) -> None: def test_is_available_returns_true_when_framework_exists() -> None: assert AvailableFrameworkAdapter().is_available() is True + + +class MissingFrameworkAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "_agent_assembly_missing_framework_" + + def get_supported_versions(self) -> list[str]: + return [] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_is_available_returns_false_when_framework_is_missing() -> None: + assert MissingFrameworkAdapter().is_available() is False From a409bbf1e34cd6597625b4fba5cff2901c784107 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:22:20 +0800 Subject: [PATCH 29/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20test=20for?= =?UTF-8?q?=20get=5Factive=5Fversion()=20with=20=5F=5Fversion=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index ef38c0a..17647e1 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -47,3 +47,21 @@ def unregister_hooks(self) -> None: def test_is_available_returns_false_when_framework_is_missing() -> None: assert MissingFrameworkAdapter().is_available() is False + + +class VersionedFrameworkAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "pytest" + + def get_supported_versions(self) -> list[str]: + return [] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_get_active_version_returns_module_version() -> None: + assert VersionedFrameworkAdapter().get_active_version() is not None From 22f2d686e3188191642c5ac6c7fdf13274508d1d Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:22:29 +0800 Subject: [PATCH 30/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20test=20for?= =?UTF-8?q?=20get=5Factive=5Fversion()=20without=20=5F=5Fversion=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index 17647e1..21e8a3f 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -65,3 +65,21 @@ def unregister_hooks(self) -> None: def test_get_active_version_returns_module_version() -> None: assert VersionedFrameworkAdapter().get_active_version() is not None + + +class NonVersionedFrameworkAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "math" + + def get_supported_versions(self) -> list[str]: + return [] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_get_active_version_returns_none_without_version() -> None: + assert NonVersionedFrameworkAdapter().get_active_version() is None From 86f1fbc51e2ab1128f415c5ee89e411dbfef1c00 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 09:22:36 +0800 Subject: [PATCH 31/34] =?UTF-8?q?=F0=9F=94=A7=20(typing):=20Enforce=20stri?= =?UTF-8?q?ct=20type-check=20coverage=20for=20new=20adapter=20base=20modul?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mypy.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy.ini b/mypy.ini index 9d88229..70c6cf5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,3 +20,6 @@ warn_unused_ignores = True ;no_implicit_reexport = True strict_equality = True strict_concatenate = True + +[mypy-agent_assembly.adapters.base] +strict = True From 4c158abc3727d5a3f61abafa5d401c7bad8e62fc Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 11:31:46 +0800 Subject: [PATCH 32/34] =?UTF-8?q?=E2=9C=A8=20(adapters):=20Add=20registrat?= =?UTF-8?q?ion-time=20contract=20validation=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 25 +++++++++++++++++++ test/unit/adapters/test_base.py | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 8e30139..50272b4 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -4,6 +4,8 @@ import importlib from typing import Protocol +from agent_assembly.exceptions import AdapterValidationError + class GovernanceInterceptor(Protocol): """Protocol implemented by governance interceptors used by adapters.""" @@ -38,6 +40,29 @@ def unregister_hooks(self) -> None: ... + def validate_registration(self) -> None: + framework_name = self.get_framework_name() + if not framework_name.strip(): + raise AdapterValidationError( + "Adapter contract invalid: framework name must be non-empty." + ) + + supported_versions = self.get_supported_versions() + if not supported_versions: + raise AdapterValidationError( + "Adapter contract invalid: supported versions must not be empty." + ) + + for version_range in supported_versions: + if not version_range.strip(): + raise AdapterValidationError( + "Adapter contract invalid: version ranges must be non-empty strings." + ) + + def register(self, interceptor: GovernanceInterceptor) -> None: + self.validate_registration() + self.register_hooks(interceptor) + def is_available(self) -> bool: """Return True when the framework package can be imported.""" diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index 21e8a3f..cc81bb4 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -1,6 +1,7 @@ import pytest from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor +from agent_assembly.exceptions import AdapterValidationError class IncompleteAdapter(FrameworkAdapter): @@ -83,3 +84,45 @@ def unregister_hooks(self) -> None: def test_get_active_version_returns_none_without_version() -> None: assert NonVersionedFrameworkAdapter().get_active_version() is None + + +class InvalidRegistrationAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return " " + + def get_supported_versions(self) -> list[str]: + return [] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_register_raises_validation_error_for_invalid_contract() -> None: + with pytest.raises(AdapterValidationError): + InvalidRegistrationAdapter().register(object()) + + +class ValidRegistrationAdapter(FrameworkAdapter): + def __init__(self) -> None: + self.hooks_registered = False + + def get_framework_name(self) -> str: + return "pytest" + + def get_supported_versions(self) -> list[str]: + return [">=8.0.0,<9.0.0"] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + self.hooks_registered = True + + def unregister_hooks(self) -> None: + return None + + +def test_register_calls_register_hooks_when_contract_is_valid() -> None: + adapter = ValidRegistrationAdapter() + adapter.register(object()) + assert adapter.hooks_registered is True From 91ee05edbf518879b457a77b64bbd5537933b977 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 11:32:12 +0800 Subject: [PATCH 33/34] =?UTF-8?q?=E2=9C=85=20(tests):=20Add=20LangChain=20?= =?UTF-8?q?coverage=20for=20adapter=20default=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/adapters/test_base.py | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/unit/adapters/test_base.py b/test/unit/adapters/test_base.py index cc81bb4..d32ce31 100644 --- a/test/unit/adapters/test_base.py +++ b/test/unit/adapters/test_base.py @@ -1,4 +1,5 @@ import pytest +from types import SimpleNamespace from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor from agent_assembly.exceptions import AdapterValidationError @@ -126,3 +127,46 @@ def test_register_calls_register_hooks_when_contract_is_valid() -> None: adapter = ValidRegistrationAdapter() adapter.register(object()) assert adapter.hooks_registered is True + + +class LangChainFrameworkAdapter(FrameworkAdapter): + def get_framework_name(self) -> str: + return "langchain" + + def get_supported_versions(self) -> list[str]: + return [">=0.1.0,<0.4.0"] + + def register_hooks(self, interceptor: GovernanceInterceptor) -> None: + return None + + def unregister_hooks(self) -> None: + return None + + +def test_is_available_returns_true_for_langchain(monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[str] = [] + + def fake_import_module(module_name: str) -> object: + calls.append(module_name) + if module_name == "langchain": + return SimpleNamespace(__version__="0.3.0") + raise ImportError + + monkeypatch.setattr("agent_assembly.adapters.base.importlib.import_module", fake_import_module) + + adapter = LangChainFrameworkAdapter() + assert adapter.is_available() is True + assert calls == ["langchain"] + + +def test_get_active_version_returns_langchain_version( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def fake_import_module(module_name: str) -> object: + if module_name == "langchain": + return SimpleNamespace(__version__="0.3.0") + raise ImportError + + monkeypatch.setattr("agent_assembly.adapters.base.importlib.import_module", fake_import_module) + + assert LangChainFrameworkAdapter().get_active_version() == "0.3.0" From 1f61cf86c142d2a195fb2217d4f053ab48cccd9f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Mon, 27 Apr 2026 11:32:44 +0800 Subject: [PATCH 34/34] =?UTF-8?q?=F0=9F=93=9D=20(adapters):=20Clarify=20co?= =?UTF-8?q?ntract=20docstrings=20with=20error=20conditions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/adapters/base.py | 57 +++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/agent_assembly/adapters/base.py b/agent_assembly/adapters/base.py index 50272b4..ca06325 100644 --- a/agent_assembly/adapters/base.py +++ b/agent_assembly/adapters/base.py @@ -14,33 +14,62 @@ class GovernanceInterceptor(Protocol): class FrameworkAdapter(ABC): - """Abstract contract implemented by every framework adapter.""" + """Abstract contract implemented by every framework adapter. + + Adapters should be registered through `register()` so contract validation + errors are raised before framework hooks are attached. + """ @abstractmethod def get_framework_name(self) -> str: - """Return the canonical importable framework package name.""" + """Return the canonical importable framework package name. + + Error conditions: + - Empty or whitespace-only names trigger `AdapterValidationError` + during `register()`. + """ ... @abstractmethod def get_supported_versions(self) -> list[str]: - """Return supported semantic version ranges for the framework.""" + """Return supported semantic version ranges for the framework. + + Error conditions: + - Empty lists or empty range strings trigger `AdapterValidationError` + during `register()`. + """ ... @abstractmethod def register_hooks(self, interceptor: GovernanceInterceptor) -> None: - """Attach framework hooks to a governance interceptor instance.""" + """Attach framework hooks to a governance interceptor instance. + + Error conditions: + - Framework-specific hook wiring failures should raise adapter-specific + exceptions for the caller to handle. + """ ... @abstractmethod def unregister_hooks(self) -> None: - """Detach all framework hooks in an idempotent way.""" + """Detach all framework hooks in an idempotent way. + + Error conditions: + - This method should avoid raising when no hooks are currently active. + """ ... def validate_registration(self) -> None: + """Validate adapter contract values before hook registration. + + Raises: + AdapterValidationError: If the framework name or version ranges + violate the base adapter contract. + """ framework_name = self.get_framework_name() if not framework_name.strip(): raise AdapterValidationError( @@ -60,11 +89,20 @@ def validate_registration(self) -> None: ) def register(self, interceptor: GovernanceInterceptor) -> None: + """Validate contract values and then attach framework hooks. + + Raises: + AdapterValidationError: If `validate_registration()` fails. + """ self.validate_registration() self.register_hooks(interceptor) def is_available(self) -> bool: - """Return True when the framework package can be imported.""" + """Return True when the framework package can be imported. + + Error conditions: + - Import failures are handled internally and return `False`. + """ try: importlib.import_module(self.get_framework_name()) @@ -74,7 +112,12 @@ def is_available(self) -> bool: return True def get_active_version(self) -> str | None: - """Return framework __version__ when present, otherwise None.""" + """Return framework `__version__` when present, otherwise `None`. + + Error conditions: + - Import failures and missing/non-string `__version__` values return + `None`. + """ try: module = importlib.import_module(self.get_framework_name())