agent-assembly 0.0.2__py3-none-any.whl
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.
- agent_assembly/__init__.py +122 -0
- agent_assembly/_install.py +56 -0
- agent_assembly/adapters/__init__.py +4 -0
- agent_assembly/adapters/base.py +158 -0
- agent_assembly/adapters/crewai/__init__.py +6 -0
- agent_assembly/adapters/crewai/adapter.py +28 -0
- agent_assembly/adapters/crewai/patch.py +484 -0
- agent_assembly/adapters/google_adk/__init__.py +6 -0
- agent_assembly/adapters/google_adk/adapter.py +51 -0
- agent_assembly/adapters/google_adk/patch.py +357 -0
- agent_assembly/adapters/langchain/__init__.py +21 -0
- agent_assembly/adapters/langchain/adapter.py +47 -0
- agent_assembly/adapters/langchain/callback_handler.py +320 -0
- agent_assembly/adapters/langchain/langgraph_patch.py +33 -0
- agent_assembly/adapters/langchain/patch.py +36 -0
- agent_assembly/adapters/langchain/runtime.py +44 -0
- agent_assembly/adapters/langgraph/__init__.py +6 -0
- agent_assembly/adapters/langgraph/adapter.py +40 -0
- agent_assembly/adapters/langgraph/patch.py +574 -0
- agent_assembly/adapters/mcp/__init__.py +6 -0
- agent_assembly/adapters/mcp/adapter.py +40 -0
- agent_assembly/adapters/mcp/patch.py +311 -0
- agent_assembly/adapters/openai_agents/__init__.py +6 -0
- agent_assembly/adapters/openai_agents/adapter.py +49 -0
- agent_assembly/adapters/openai_agents/patch.py +487 -0
- agent_assembly/adapters/pydantic_ai/__init__.py +6 -0
- agent_assembly/adapters/pydantic_ai/adapter.py +40 -0
- agent_assembly/adapters/pydantic_ai/patch.py +403 -0
- agent_assembly/adapters/registry.py +213 -0
- agent_assembly/cli/__init__.py +1 -0
- agent_assembly/cli/adapter_validator.py +295 -0
- agent_assembly/cli/main.py +61 -0
- agent_assembly/cli/output.py +24 -0
- agent_assembly/client/__init__.py +6 -0
- agent_assembly/client/dispatch.py +28 -0
- agent_assembly/client/emitter.py +50 -0
- agent_assembly/client/gateway.py +218 -0
- agent_assembly/core/__init__.py +29 -0
- agent_assembly/core/assembly.py +326 -0
- agent_assembly/core/gateway_resolver.py +183 -0
- agent_assembly/core/lineage.py +47 -0
- agent_assembly/core/spawn.py +32 -0
- agent_assembly/exceptions/__init__.py +77 -0
- agent_assembly/models/__init__.py +5 -0
- agent_assembly/models/agent.py +37 -0
- agent_assembly/op_control.py +216 -0
- agent_assembly/proto/__init__.py +0 -0
- agent_assembly/proto/common_pb2.py +44 -0
- agent_assembly/proto/common_pb2.pyi +65 -0
- agent_assembly/proto/common_pb2_grpc.py +24 -0
- agent_assembly/proto/policy_pb2.py +67 -0
- agent_assembly/proto/policy_pb2.pyi +160 -0
- agent_assembly/proto/policy_pb2_grpc.py +208 -0
- agent_assembly/py.typed +0 -0
- agent_assembly/runtime.py +134 -0
- agent_assembly/types.py +145 -0
- agent_assembly-0.0.2.dist-info/METADATA +222 -0
- agent_assembly-0.0.2.dist-info/RECORD +61 -0
- agent_assembly-0.0.2.dist-info/WHEEL +4 -0
- agent_assembly-0.0.2.dist-info/entry_points.txt +2 -0
- agent_assembly-0.0.2.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Agent Assembly Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import importlib
|
|
7
|
+
import importlib.util
|
|
8
|
+
import sys
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
__version__ = "0.0.1a2"
|
|
12
|
+
|
|
13
|
+
# AAASM-1696: top-level exports are resolved lazily so that lightweight
|
|
14
|
+
# submodules (e.g. `agent_assembly.runtime`, which is stdlib-only) can be
|
|
15
|
+
# imported without dragging in the SDK's third-party dependency surface
|
|
16
|
+
# (`httpx`, `pydantic`, …). See PEP 562.
|
|
17
|
+
_LAZY_EXPORTS: dict[str, str] = {
|
|
18
|
+
"init_assembly": "agent_assembly.core",
|
|
19
|
+
"AssemblyContext": "agent_assembly.core",
|
|
20
|
+
"GovernanceInterceptor": "agent_assembly.adapters",
|
|
21
|
+
"FrameworkAdapter": "agent_assembly.adapters",
|
|
22
|
+
"AssemblyError": "agent_assembly.exceptions",
|
|
23
|
+
"AgentError": "agent_assembly.exceptions",
|
|
24
|
+
"PolicyError": "agent_assembly.exceptions",
|
|
25
|
+
"GatewayError": "agent_assembly.exceptions",
|
|
26
|
+
"ConfigurationError": "agent_assembly.exceptions",
|
|
27
|
+
"AdapterValidationError": "agent_assembly.exceptions",
|
|
28
|
+
"ToolExecutionBlockedError": "agent_assembly.exceptions",
|
|
29
|
+
"MCPToolBlockedError": "agent_assembly.exceptions",
|
|
30
|
+
"AuditEvent": "agent_assembly.types",
|
|
31
|
+
"CallStackNode": "agent_assembly.types",
|
|
32
|
+
"CallStackNodeKind": "agent_assembly.types",
|
|
33
|
+
"GovernanceEvent": "agent_assembly._core",
|
|
34
|
+
"PolicyResult": "agent_assembly._core",
|
|
35
|
+
"PolicyTimeoutError": "agent_assembly._core",
|
|
36
|
+
"RuntimeClient": "agent_assembly._core",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_ALWAYS_EXPORTED: list[str] = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"init_assembly",
|
|
42
|
+
"AssemblyContext",
|
|
43
|
+
"GovernanceInterceptor",
|
|
44
|
+
"FrameworkAdapter",
|
|
45
|
+
"AssemblyError",
|
|
46
|
+
"AgentError",
|
|
47
|
+
"PolicyError",
|
|
48
|
+
"GatewayError",
|
|
49
|
+
"ConfigurationError",
|
|
50
|
+
"AdapterValidationError",
|
|
51
|
+
"ToolExecutionBlockedError",
|
|
52
|
+
"MCPToolBlockedError",
|
|
53
|
+
"AuditEvent",
|
|
54
|
+
"CallStackNode",
|
|
55
|
+
"CallStackNodeKind",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
_OPTIONAL_CORE: list[str] = [
|
|
59
|
+
"RuntimeClient",
|
|
60
|
+
"GovernanceEvent",
|
|
61
|
+
"PolicyResult",
|
|
62
|
+
"PolicyTimeoutError",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _core_available() -> bool:
|
|
67
|
+
if "agent_assembly._core" in sys.modules:
|
|
68
|
+
return True
|
|
69
|
+
try:
|
|
70
|
+
return importlib.util.find_spec("agent_assembly._core") is not None
|
|
71
|
+
except (ModuleNotFoundError, ValueError):
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__: list[str] = list(_ALWAYS_EXPORTED)
|
|
76
|
+
if _core_available():
|
|
77
|
+
__all__.extend(_OPTIONAL_CORE)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def __getattr__(name: str) -> Any:
|
|
81
|
+
module_name = _LAZY_EXPORTS.get(name)
|
|
82
|
+
if module_name is None:
|
|
83
|
+
raise AttributeError(f"module 'agent_assembly' has no attribute {name!r}")
|
|
84
|
+
try:
|
|
85
|
+
module = importlib.import_module(module_name)
|
|
86
|
+
except ImportError:
|
|
87
|
+
if module_name == "agent_assembly._core":
|
|
88
|
+
raise AttributeError(
|
|
89
|
+
f"module 'agent_assembly' has no attribute {name!r}: the native '_core' extension is not built"
|
|
90
|
+
) from None
|
|
91
|
+
raise
|
|
92
|
+
value = getattr(module, name)
|
|
93
|
+
globals()[name] = value
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def __dir__() -> list[str]:
|
|
98
|
+
return sorted(set(__all__) | set(globals()))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if TYPE_CHECKING:
|
|
102
|
+
from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor
|
|
103
|
+
from agent_assembly.core import AssemblyContext, init_assembly
|
|
104
|
+
from agent_assembly.exceptions import (
|
|
105
|
+
AdapterValidationError,
|
|
106
|
+
AgentError,
|
|
107
|
+
AssemblyError,
|
|
108
|
+
ConfigurationError,
|
|
109
|
+
GatewayError,
|
|
110
|
+
MCPToolBlockedError,
|
|
111
|
+
PolicyError,
|
|
112
|
+
ToolExecutionBlockedError,
|
|
113
|
+
)
|
|
114
|
+
from agent_assembly.types import AuditEvent, CallStackNode, CallStackNodeKind
|
|
115
|
+
|
|
116
|
+
with contextlib.suppress(ImportError):
|
|
117
|
+
from agent_assembly._core import (
|
|
118
|
+
GovernanceEvent,
|
|
119
|
+
PolicyResult,
|
|
120
|
+
PolicyTimeoutError,
|
|
121
|
+
RuntimeClient,
|
|
122
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Install-time runtime binary resolution for the agent-assembly Python SDK.
|
|
2
|
+
|
|
3
|
+
This module is the lean, blocking presence check for the ``aasm`` sidecar
|
|
4
|
+
binary. It is intentionally separate from :mod:`agent_assembly.runtime`,
|
|
5
|
+
which manages the full lifecycle (port probe + subprocess spawn). The
|
|
6
|
+
intended use is at import time or at the start of long-running scripts:
|
|
7
|
+
fail fast with a clear install hint when the binary is unavailable, before
|
|
8
|
+
the user discovers it via a subtle subprocess failure deep in the SDK call.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BINARY_NAME",
|
|
19
|
+
"INSTALL_HINT",
|
|
20
|
+
"WHEEL_BUNDLED_BIN",
|
|
21
|
+
"ensure_runtime",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
BINARY_NAME = "aasm"
|
|
25
|
+
|
|
26
|
+
# Path where the platform-wheel ([runtime] extra) bundles the sidecar binary.
|
|
27
|
+
# Mirrors the location runtime.py's find_aasm_binary() also searches, so
|
|
28
|
+
# both modules observe the same wheel artifact without coordination.
|
|
29
|
+
WHEEL_BUNDLED_BIN = Path(__file__).resolve().parent / "bin" / BINARY_NAME
|
|
30
|
+
|
|
31
|
+
INSTALL_HINT = (
|
|
32
|
+
"agent-assembly runtime binary `aasm` was not found.\n"
|
|
33
|
+
" Install the platform wheel: pip install agent-assembly[runtime]\n"
|
|
34
|
+
" Or install manually: brew install agent-assembly/tap/aasm\n"
|
|
35
|
+
" curl -fsSL https://get.agent-assembly.io | sh"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ensure_runtime() -> Path:
|
|
40
|
+
"""Return the resolved path to the ``aasm`` sidecar binary.
|
|
41
|
+
|
|
42
|
+
Search order, fast to slow:
|
|
43
|
+
|
|
44
|
+
1. ``$PATH`` (Homebrew tap, ``cargo install``, ``curl`` installer default).
|
|
45
|
+
2. ``agent_assembly/bin/aasm`` bundled by the ``[runtime]`` platform wheel.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
RuntimeError: when no binary is found on either path. The message
|
|
49
|
+
carries :data:`INSTALL_HINT` with copy-paste install commands.
|
|
50
|
+
"""
|
|
51
|
+
path_hit = shutil.which(BINARY_NAME)
|
|
52
|
+
if path_hit:
|
|
53
|
+
return Path(path_hit)
|
|
54
|
+
if WHEEL_BUNDLED_BIN.is_file() and os.access(WHEEL_BUNDLED_BIN, os.X_OK):
|
|
55
|
+
return WHEEL_BUNDLED_BIN
|
|
56
|
+
raise RuntimeError(INSTALL_HINT)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from agent_assembly.exceptions import AdapterValidationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GovernanceInterceptor(Protocol):
|
|
11
|
+
"""Protocol implemented by governance interceptors used by adapters."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FrameworkAdapter(ABC):
|
|
15
|
+
"""Abstract contract implemented by every framework adapter.
|
|
16
|
+
|
|
17
|
+
This is the **public adapter API** — the interface that SDK users and
|
|
18
|
+
third-party plugin authors interact with. Each concrete adapter represents
|
|
19
|
+
one AI framework (e.g. LangChain, CrewAI) and knows how to install
|
|
20
|
+
governance hooks for that framework.
|
|
21
|
+
|
|
22
|
+
The two key lifecycle methods are:
|
|
23
|
+
|
|
24
|
+
- ``register_hooks(interceptor)`` — install framework-specific
|
|
25
|
+
monkey-patches that route intercepted calls through the governance
|
|
26
|
+
interceptor. Internally, each adapter delegates to one or more
|
|
27
|
+
``RuntimePatch`` instances whose ``apply()`` method performs the
|
|
28
|
+
actual monkey-patching.
|
|
29
|
+
|
|
30
|
+
- ``unregister_hooks()`` — tear down all patches installed by this
|
|
31
|
+
adapter, delegating to each patch's ``revert()`` method.
|
|
32
|
+
|
|
33
|
+
Adapters are discovered and activated by ``AdapterRegistry.auto_detect()``
|
|
34
|
+
which is the single detection path used by ``init_assembly()``.
|
|
35
|
+
|
|
36
|
+
Adapters should be registered through ``register()`` so contract
|
|
37
|
+
validation errors are raised before framework hooks are attached.
|
|
38
|
+
|
|
39
|
+
See Also:
|
|
40
|
+
``RuntimePatch`` in ``core/assembly.py`` — the internal
|
|
41
|
+
monkey-patch protocol with ``apply()`` / ``revert()`` methods.
|
|
42
|
+
ADR-0001 (``docs/adr/0001-hook-architecture.md``) for the full
|
|
43
|
+
architecture rationale.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def get_framework_name(self) -> str:
|
|
48
|
+
"""Return the canonical importable framework package name.
|
|
49
|
+
|
|
50
|
+
Error conditions:
|
|
51
|
+
- Empty or whitespace-only names trigger `AdapterValidationError`
|
|
52
|
+
during `register()`.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def get_supported_versions(self) -> list[str]:
|
|
59
|
+
"""Return supported semantic version ranges for the framework.
|
|
60
|
+
|
|
61
|
+
Error conditions:
|
|
62
|
+
- Empty lists or empty range strings trigger `AdapterValidationError`
|
|
63
|
+
during `register()`.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def register_hooks(self, interceptor: GovernanceInterceptor) -> None:
|
|
70
|
+
"""Attach framework hooks to a governance interceptor instance.
|
|
71
|
+
|
|
72
|
+
Error conditions:
|
|
73
|
+
- Framework-specific hook wiring failures should raise adapter-specific
|
|
74
|
+
exceptions for the caller to handle.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def unregister_hooks(self) -> None:
|
|
81
|
+
"""Detach all framework hooks in an idempotent way.
|
|
82
|
+
|
|
83
|
+
Error conditions:
|
|
84
|
+
- This method should avoid raising when no hooks are currently active.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
def validate_registration(self) -> None:
|
|
90
|
+
"""Validate adapter contract values before hook registration.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
AdapterValidationError: If the framework name or version ranges
|
|
94
|
+
violate the base adapter contract.
|
|
95
|
+
"""
|
|
96
|
+
framework_name = self.get_framework_name()
|
|
97
|
+
if not framework_name.strip():
|
|
98
|
+
raise AdapterValidationError("Adapter contract invalid: framework name must be non-empty.")
|
|
99
|
+
|
|
100
|
+
supported_versions = self.get_supported_versions()
|
|
101
|
+
if not supported_versions:
|
|
102
|
+
raise AdapterValidationError("Adapter contract invalid: supported versions must not be empty.")
|
|
103
|
+
|
|
104
|
+
for version_range in supported_versions:
|
|
105
|
+
if not version_range.strip():
|
|
106
|
+
raise AdapterValidationError("Adapter contract invalid: version ranges must be non-empty strings.")
|
|
107
|
+
|
|
108
|
+
def register(self, interceptor: GovernanceInterceptor) -> None:
|
|
109
|
+
"""Validate contract values and then attach framework hooks.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
AdapterValidationError: If `validate_registration()` fails.
|
|
113
|
+
"""
|
|
114
|
+
self.validate_registration()
|
|
115
|
+
self.register_hooks(interceptor)
|
|
116
|
+
|
|
117
|
+
def is_available(self) -> bool:
|
|
118
|
+
"""Return True when the framework package can be imported.
|
|
119
|
+
|
|
120
|
+
Error conditions:
|
|
121
|
+
- Import failures are handled internally and return `False`.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
importlib.import_module(self.get_framework_name())
|
|
126
|
+
except ImportError:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
def set_process_agent_id(self, agent_id: str | None) -> None:
|
|
132
|
+
"""Set the process-level agent ID for governance event attribution.
|
|
133
|
+
|
|
134
|
+
Adapters that need an agent ID (e.g. LangChain, OpenAI Agents, MCP)
|
|
135
|
+
override ``process_agent_id`` as a property. This base method is
|
|
136
|
+
a no-op for adapters that do not use an agent ID.
|
|
137
|
+
"""
|
|
138
|
+
if hasattr(self, "process_agent_id"):
|
|
139
|
+
self.process_agent_id = agent_id
|
|
140
|
+
|
|
141
|
+
def get_active_version(self) -> str | None:
|
|
142
|
+
"""Return framework `__version__` when present, otherwise `None`.
|
|
143
|
+
|
|
144
|
+
Error conditions:
|
|
145
|
+
- Import failures and missing/non-string `__version__` values return
|
|
146
|
+
`None`.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
module = importlib.import_module(self.get_framework_name())
|
|
151
|
+
except ImportError:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
version = getattr(module, "__version__", None)
|
|
155
|
+
if isinstance(version, str):
|
|
156
|
+
return version
|
|
157
|
+
|
|
158
|
+
return None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""CrewAI framework adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from agent_assembly.adapters.base import FrameworkAdapter, GovernanceInterceptor
|
|
6
|
+
from agent_assembly.adapters.crewai.patch import CrewAIPatch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CrewAIAdapter(FrameworkAdapter):
|
|
10
|
+
"""Adapter for CrewAI framework governance hook installation."""
|
|
11
|
+
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._patch: CrewAIPatch | None = None
|
|
14
|
+
|
|
15
|
+
def get_framework_name(self) -> str:
|
|
16
|
+
return "crewai"
|
|
17
|
+
|
|
18
|
+
def get_supported_versions(self) -> list[str]:
|
|
19
|
+
return [">=0.1.0"]
|
|
20
|
+
|
|
21
|
+
def register_hooks(self, interceptor: GovernanceInterceptor) -> None:
|
|
22
|
+
self._patch = CrewAIPatch(interceptor)
|
|
23
|
+
self._patch.apply()
|
|
24
|
+
|
|
25
|
+
def unregister_hooks(self) -> None:
|
|
26
|
+
if self._patch is not None:
|
|
27
|
+
self._patch.revert()
|
|
28
|
+
self._patch = None
|