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.
Files changed (61) hide show
  1. agent_assembly/__init__.py +122 -0
  2. agent_assembly/_install.py +56 -0
  3. agent_assembly/adapters/__init__.py +4 -0
  4. agent_assembly/adapters/base.py +158 -0
  5. agent_assembly/adapters/crewai/__init__.py +6 -0
  6. agent_assembly/adapters/crewai/adapter.py +28 -0
  7. agent_assembly/adapters/crewai/patch.py +484 -0
  8. agent_assembly/adapters/google_adk/__init__.py +6 -0
  9. agent_assembly/adapters/google_adk/adapter.py +51 -0
  10. agent_assembly/adapters/google_adk/patch.py +357 -0
  11. agent_assembly/adapters/langchain/__init__.py +21 -0
  12. agent_assembly/adapters/langchain/adapter.py +47 -0
  13. agent_assembly/adapters/langchain/callback_handler.py +320 -0
  14. agent_assembly/adapters/langchain/langgraph_patch.py +33 -0
  15. agent_assembly/adapters/langchain/patch.py +36 -0
  16. agent_assembly/adapters/langchain/runtime.py +44 -0
  17. agent_assembly/adapters/langgraph/__init__.py +6 -0
  18. agent_assembly/adapters/langgraph/adapter.py +40 -0
  19. agent_assembly/adapters/langgraph/patch.py +574 -0
  20. agent_assembly/adapters/mcp/__init__.py +6 -0
  21. agent_assembly/adapters/mcp/adapter.py +40 -0
  22. agent_assembly/adapters/mcp/patch.py +311 -0
  23. agent_assembly/adapters/openai_agents/__init__.py +6 -0
  24. agent_assembly/adapters/openai_agents/adapter.py +49 -0
  25. agent_assembly/adapters/openai_agents/patch.py +487 -0
  26. agent_assembly/adapters/pydantic_ai/__init__.py +6 -0
  27. agent_assembly/adapters/pydantic_ai/adapter.py +40 -0
  28. agent_assembly/adapters/pydantic_ai/patch.py +403 -0
  29. agent_assembly/adapters/registry.py +213 -0
  30. agent_assembly/cli/__init__.py +1 -0
  31. agent_assembly/cli/adapter_validator.py +295 -0
  32. agent_assembly/cli/main.py +61 -0
  33. agent_assembly/cli/output.py +24 -0
  34. agent_assembly/client/__init__.py +6 -0
  35. agent_assembly/client/dispatch.py +28 -0
  36. agent_assembly/client/emitter.py +50 -0
  37. agent_assembly/client/gateway.py +218 -0
  38. agent_assembly/core/__init__.py +29 -0
  39. agent_assembly/core/assembly.py +326 -0
  40. agent_assembly/core/gateway_resolver.py +183 -0
  41. agent_assembly/core/lineage.py +47 -0
  42. agent_assembly/core/spawn.py +32 -0
  43. agent_assembly/exceptions/__init__.py +77 -0
  44. agent_assembly/models/__init__.py +5 -0
  45. agent_assembly/models/agent.py +37 -0
  46. agent_assembly/op_control.py +216 -0
  47. agent_assembly/proto/__init__.py +0 -0
  48. agent_assembly/proto/common_pb2.py +44 -0
  49. agent_assembly/proto/common_pb2.pyi +65 -0
  50. agent_assembly/proto/common_pb2_grpc.py +24 -0
  51. agent_assembly/proto/policy_pb2.py +67 -0
  52. agent_assembly/proto/policy_pb2.pyi +160 -0
  53. agent_assembly/proto/policy_pb2_grpc.py +208 -0
  54. agent_assembly/py.typed +0 -0
  55. agent_assembly/runtime.py +134 -0
  56. agent_assembly/types.py +145 -0
  57. agent_assembly-0.0.2.dist-info/METADATA +222 -0
  58. agent_assembly-0.0.2.dist-info/RECORD +61 -0
  59. agent_assembly-0.0.2.dist-info/WHEEL +4 -0
  60. agent_assembly-0.0.2.dist-info/entry_points.txt +2 -0
  61. 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,4 @@
1
+ from agent_assembly.adapters.base import FrameworkAdapter, GovernanceInterceptor
2
+ from agent_assembly.adapters.registry import AdapterInfo, AdapterRegistry
3
+
4
+ __all__ = ["GovernanceInterceptor", "FrameworkAdapter", "AdapterInfo", "AdapterRegistry"]
@@ -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,6 @@
1
+ """CrewAI adapter package."""
2
+
3
+ from agent_assembly.adapters.crewai.adapter import CrewAIAdapter
4
+ from agent_assembly.adapters.crewai.patch import CrewAIPatch
5
+
6
+ __all__ = ["CrewAIAdapter", "CrewAIPatch"]
@@ -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