Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
650786a
🔧 (mypy): Skip type-checking generated proto stubs
Chisanan232 Jun 15, 2026
be746e9
🔧 (pre-commit): Run mypy in the uv project env via packages discovery
Chisanan232 Jun 15, 2026
3e567b6
🚨 (client): Annotate GatewayClient dict type params and returns
Chisanan232 Jun 15, 2026
13928cd
🚨 (client): Annotate EdgeEmitter metadata dict type params
Chisanan232 Jun 15, 2026
6878cad
🚨 (models): Add type args to AgentState metadata dict field
Chisanan232 Jun 15, 2026
c06cc29
🚨 (core): Type AssemblyContext.__exit__ as Literal[False]
Chisanan232 Jun 15, 2026
c23ca52
🚨 (types): Drop redundant import-not-found ignore in to_wire_bytes
Chisanan232 Jun 15, 2026
4d01d1f
🚨 (init): Make TYPE_CHECKING imports explicit re-exports
Chisanan232 Jun 15, 2026
9eab2e2
🚨 (adapters): Explicitly re-export importlib from patch modules
Chisanan232 Jun 15, 2026
0f1ea8c
✅ (test): Type dispatch_tool patched-client context manager and payload
Chisanan232 Jun 15, 2026
462eacf
✅ (test): Annotate OpControlSubscriber fixture and test signatures
Chisanan232 Jun 15, 2026
ee3598d
✅ (test): Annotate spawn-context tests and drop stale resolver ignore
Chisanan232 Jun 15, 2026
98e8bc2
✅ (test): Annotate assembly/install/init-export unit tests
Chisanan232 Jun 15, 2026
eea706e
✅ (test): Scope comparison-overlap ignore on wire round-trip "unknown"
Chisanan232 Jun 15, 2026
63c4d4c
✅ (test): Scope abstract/attr-defined ignores in adapter base tests
Chisanan232 Jun 15, 2026
85d9bac
✅ (test): Annotate langgraph spawn-patch and edge-emission tests
Chisanan232 Jun 15, 2026
f78bb8f
✅ (test): Annotate pydantic-ai spawn-patch test method swaps
Chisanan232 Jun 15, 2026
2dd5de0
✅ (test): Annotate crewai spawn-context and patch tests
Chisanan232 Jun 15, 2026
85ef77d
✅ (test): Annotate openai-agents and mcp adapter tests
Chisanan232 Jun 15, 2026
e3bb734
✅ (test): Annotate spawn-lineage, native-core and crewai integration …
Chisanan232 Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,25 @@ repos:
build|
dist"""

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.16.0
- repo: local
hooks:
- id: mypy
package: agent_assembly,test
# exclude: ^test/unit_test.{1,64}.py
name: mypy
# Run mypy inside the uv-managed project env so it resolves the SDK's
# runtime + optional framework deps (pydantic, httpx, pydantic-ai, …).
# The upstream mirrors-mypy hook installs into an isolated venv that lacks
# these, which made every BaseModel/framework subclass resolve to `Any`.
# Type-check the whole project via mypy.ini's `packages =` discovery (no
# file paths) so the generated proto .py/.pyi pairs don't trip a
# "Duplicate module" error and the hook stays identical to the configured
# `mypy` invocation.
entry: uv run mypy
language: system
types: [python]
pass_filenames: false
args:
# - --strict
- --ignore-missing-imports
- --show-traceback
additional_dependencies: [types-PyYAML>=6.0.12.9]

- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
Expand Down
34 changes: 19 additions & 15 deletions agent_assembly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,26 @@ def __dir__() -> list[str]:


if TYPE_CHECKING:
from agent_assembly.adapters import FrameworkAdapter, GovernanceInterceptor
from agent_assembly.core import AssemblyContext, init_assembly
from agent_assembly.adapters import FrameworkAdapter as FrameworkAdapter
from agent_assembly.adapters import GovernanceInterceptor as GovernanceInterceptor
from agent_assembly.core import AssemblyContext as AssemblyContext
from agent_assembly.core import init_assembly as init_assembly
from agent_assembly.exceptions import (
AdapterValidationError,
AgentError,
AssemblyError,
ConfigurationError,
GatewayError,
MCPToolBlockedError,
PolicyError,
ToolExecutionBlockedError,
AdapterValidationError as AdapterValidationError,
)
from agent_assembly.types import AuditEvent, CallStackNode, CallStackNodeKind
from agent_assembly.exceptions import AgentError as AgentError
from agent_assembly.exceptions import AssemblyError as AssemblyError
from agent_assembly.exceptions import ConfigurationError as ConfigurationError
from agent_assembly.exceptions import GatewayError as GatewayError
from agent_assembly.exceptions import MCPToolBlockedError as MCPToolBlockedError
from agent_assembly.exceptions import PolicyError as PolicyError
from agent_assembly.exceptions import (
ToolExecutionBlockedError as ToolExecutionBlockedError,
)
from agent_assembly.types import AuditEvent as AuditEvent
from agent_assembly.types import CallStackNode as CallStackNode
from agent_assembly.types import CallStackNodeKind as CallStackNodeKind

with contextlib.suppress(ImportError):
from agent_assembly._core import (
GovernanceEvent,
RuntimeClient,
)
from agent_assembly._core import GovernanceEvent as GovernanceEvent
from agent_assembly._core import RuntimeClient as RuntimeClient
2 changes: 1 addition & 1 deletion agent_assembly/adapters/crewai/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import importlib
import importlib as importlib
from collections.abc import Mapping
from dataclasses import dataclass
from functools import wraps
Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/adapters/google_adk/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import importlib
import importlib as importlib
import inspect
from collections.abc import Mapping
from dataclasses import dataclass
Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/adapters/mcp/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import importlib
import importlib as importlib
import importlib.util
import inspect
from dataclasses import dataclass
Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/adapters/openai_agents/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import importlib
import importlib as importlib
import importlib.util
import inspect
from dataclasses import dataclass, field
Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/adapters/pydantic_ai/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

import importlib
import importlib as importlib
import inspect
from collections.abc import Mapping
from dataclasses import dataclass
Expand Down
5 changes: 3 additions & 2 deletions agent_assembly/client/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import logging
import threading
from typing import Any

from agent_assembly.client.gateway import GatewayClient

Expand All @@ -21,7 +22,7 @@ def emit(
source_agent_id: str,
target_agent_id: str,
edge_type: str,
metadata: dict | None = None,
metadata: dict[str, Any] | None = None,
) -> None:
"""Schedule a fire-and-forget edge report on a daemon thread."""
t = threading.Thread(
Expand All @@ -36,7 +37,7 @@ def _send(
source_agent_id: str,
target_agent_id: str,
edge_type: str,
metadata: dict | None,
metadata: dict[str, Any] | None,
) -> None:
try:
self._client.report_edge(source_agent_id, target_agent_id, edge_type, metadata)
Expand Down
19 changes: 11 additions & 8 deletions agent_assembly/client/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"""Context manager exit."""
self.close()

async def register_agent(self) -> dict:
async def register_agent(self) -> dict[str, Any]:
"""
Register the agent with the governance gateway.

Expand Down Expand Up @@ -125,11 +125,12 @@
json=body if body else None,
)
response.raise_for_status()
return response.json()
data: dict[str, Any] = response.json()
return data
except httpx.HTTPError as e:
raise GatewayError(f"Failed to register agent: {e}") from e

async def check_policy_compliance(self, action: str) -> dict:
async def check_policy_compliance(self, action: str) -> dict[str, Any]:

Check warning on line 133 in agent_assembly/client/gateway.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=AI-agent-assembly_python-sdk&issues=AZ7L18tTndFLL4okhR21&open=AZ7L18tTndFLL4okhR21&pullRequest=130
"""
Check if an action complies with governance policies.

Expand All @@ -148,7 +149,8 @@
json={"action": action},
)
response.raise_for_status()
return response.json()
data: dict[str, Any] = response.json()
return data
except httpx.HTTPError as e:
raise GatewayError(f"Failed to check policy compliance: {e}") from e

Expand All @@ -157,8 +159,8 @@
source_agent_id: str,
target_agent_id: str,
edge_type: str,
metadata: dict | None = None,
) -> dict:
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""
Report a directed edge between two agents to the topology store.

Expand All @@ -176,7 +178,7 @@
"""
import json as _json

body: dict = {
body: dict[str, str] = {
"source_agent_id": source_agent_id,
"target_agent_id": target_agent_id,
"edge_type": edge_type,
Expand All @@ -186,7 +188,8 @@
try:
response = self.client.post("/topology/edges", json=body)
response.raise_for_status()
return response.json()
data: dict[str, Any] = response.json()
return data
except httpx.HTTPError as e:
raise GatewayError(f"Failed to report edge: {e}") from e

Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/core/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class AssemblyContext:
def __enter__(self) -> AssemblyContext:
return self

def __exit__(self, exc_type: object, exc: object, tb: object) -> bool:
def __exit__(self, exc_type: object, exc: object, tb: object) -> Literal[False]:
del exc_type, exc, tb
self.shutdown()
return False
Expand Down
4 changes: 2 additions & 2 deletions agent_assembly/models/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from datetime import datetime
from typing import Optional
from typing import Any, Optional

from pydantic import BaseModel, Field

Expand All @@ -25,7 +25,7 @@ class AgentState(BaseModel):
agent_id: str = Field(..., description="Unique identifier for the agent")
status: str = Field(default="idle", description="Current status of the agent")
last_activity: Optional[datetime] = Field(None, description="Last activity timestamp")
metadata: dict = Field(default_factory=dict, description="Additional state metadata")
metadata: dict[str, Any] = Field(default_factory=dict, description="Additional state metadata")


class PolicyEvaluation(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion agent_assembly/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def to_wire_bytes(self) -> bytes:
wheel (pure-Python mode).
"""
try:
from agent_assembly._core import audit_event_to_wire_bytes # type: ignore[import-not-found]
from agent_assembly._core import audit_event_to_wire_bytes
except ImportError as exc:
raise ImportError(
"AuditEvent.to_wire_bytes() requires the native "
Expand Down
7 changes: 7 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ warn_unused_ignores = True
strict_equality = True
strict_concatenate = True

# Generated gRPC/protobuf stubs (scripts/gen_proto.py). These are emitted by
# protoc + grpcio-tools and committed verbatim; a CI drift check regenerates
# them and asserts no diff, so they must not be hand-edited to satisfy mypy.
# Skip type-checking the generated output rather than annotate code we do not own.
[mypy-agent_assembly.proto.*]
ignore_errors = True

[mypy-agent_assembly.adapters.base]
strict = True

Expand Down
6 changes: 3 additions & 3 deletions test/integration/test_assembly_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


@pytest.mark.integration
def test_init_assembly_with_valid_config():
def test_init_assembly_with_valid_config() -> None:
"""Test that assembly initialization works with valid configuration."""
# This test requires a running gateway
context = init_assembly(
Expand All @@ -26,7 +26,7 @@ def test_init_assembly_with_valid_config():


@pytest.mark.integration
def test_init_assembly_with_invalid_config():
def test_init_assembly_with_invalid_config() -> None:
"""Test that assembly initialization fails with an unknown runtime mode."""
with pytest.raises(ConfigurationError):
init_assembly(
Expand All @@ -37,7 +37,7 @@ def test_init_assembly_with_invalid_config():


@pytest.mark.integration
def test_gateway_client_context_manager():
def test_gateway_client_context_manager() -> None:
"""Test that the gateway client works as a context manager."""
context = init_assembly(
gateway_url="http://localhost:8080",
Expand Down
4 changes: 2 additions & 2 deletions test/integration/test_crewai_interception_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ def test_crewai_real_task_and_tool_classes_flow_when_available(
Agent = crewai.Agent
Crew = crewai.Crew

class BlockedTool(BaseTool):
class BlockedTool(BaseTool): # type: ignore[misc,valid-type] # base is a runtime-imported framework class
name: str = "blocked_tool"
description: str = "Tool that should be blocked by governance."

def _run(self, **kwargs: object) -> str:
del kwargs
return "should-not-run"

class SafeTool(BaseTool):
class SafeTool(BaseTool): # type: ignore[misc,valid-type] # base is a runtime-imported framework class
name: str = "safe_tool"
description: str = "Tool that should remain allowed."

Expand Down
9 changes: 5 additions & 4 deletions test/integration/test_native_core_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import time
import tracemalloc
from pathlib import Path
from typing import Any

import pytest

Expand Down Expand Up @@ -150,14 +151,14 @@ def make_audit_entry_payload(index: int, *, worker_id: int = 0) -> str:


@pytest.fixture()
def native_core():
def native_core() -> Any:
if os.getenv("AAASM_RUN_NATIVE_CORE_TESTS") != "1":
pytest.skip("Set AAASM_RUN_NATIVE_CORE_TESTS=1 to run native core runtime tests.")
return pytest.importorskip("agent_assembly._core")


@pytest.mark.integration
def test_send_event_is_non_blocking(native_core) -> None:
def test_send_event_is_non_blocking(native_core: Any) -> None:
server = MockRuntimeServer()
server.start()

Expand All @@ -175,7 +176,7 @@ def test_send_event_is_non_blocking(native_core) -> None:


@pytest.mark.integration
def test_runtime_client_has_no_thread_deadlock(native_core) -> None:
def test_runtime_client_has_no_thread_deadlock(native_core: Any) -> None:
server = MockRuntimeServer()
server.start()

Expand Down Expand Up @@ -204,7 +205,7 @@ def worker(worker_id: int) -> None:


@pytest.mark.integration
def test_runtime_client_tracemalloc_leak_guard(native_core) -> None:
def test_runtime_client_tracemalloc_leak_guard(native_core: Any) -> None:
server = MockRuntimeServer()
server.start()

Expand Down
4 changes: 2 additions & 2 deletions test/integration/test_spawn_lineage_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ def check_tool_start(self, **_kw: object) -> dict[str, str]:
result = await tool._run(_Ctx(), {})
finally:
_revert_tool_run_patch(FakeTool)
FakeTool._run = original_run
FakeTool._run = original_run # type: ignore[method-assign] # reassign fake method to install/restore stub

assert result == "search-result"
assert len(captured) == 1
Expand Down Expand Up @@ -435,7 +435,7 @@ def check_tool_start(self, **_kw: object) -> dict[str, str]:
result = await FakeTool()._run(_Ctx(), {})
finally:
_revert_tool_run_patch(FakeTool)
FakeTool._run = original_run
FakeTool._run = original_run # type: ignore[method-assign] # reassign fake method to install/restore stub

assert result == "delegated"

Expand Down
Loading