Explorar o código

feat: Paper III T-Compose type checking + Json(S) subtyping (P0-1)

Implements the type system from Paper III §3.3.3:
- LamType with Str/Int/Float/Bool/Any/Json(S)/Tuple/Union type constructors
- Json(S) structural subtyping: width (extra fields OK) + depth (field type covariance)
- is_subtype() with full Definition 5 rules including numeric hierarchy
- T-Compose check at >> operator time: output(f) <: input(g) enforced
- check_compose_types() for chain compilation in compiler.py
- Type annotations on Term (input_type/output_type properties)
- parse_type_annotation() for YAML inputType/outputType fields
- Context.fork() for parallel branch isolation (prep for P0-3)
- 44 passing tests covering all subtype rules and composition checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kenny67nju hai 5 meses
pai
achega
21026cf755

+ 11 - 0
lambdagent/__init__.py

@@ -81,6 +81,12 @@ from .rag import (
     RAGTool, AgenticRAG, SimpleVectorStore, Document, SearchResult,
     create_rag,
 )
+# Paper III: Type & Effect System
+from .types import (
+    LamType, TypeTag, AgentType, AgentTypeError,
+    T_ANY, T_NONE, T_STR, T_INT, T_FLOAT, T_BOOL, T_JSON, T_TUPLE, T_UNION,
+    is_subtype, check_compose_types, parse_type_annotation, infer_type_from_value,
+)
 # Phase 1: P0 Engineering Improvements
 from .cancellation import CancellationToken, CancelledError, NullCancellationToken
 from .retry import RetryPolicy, CircuitBreaker, CircuitOpenError, with_retry, with_retry_sync
@@ -161,6 +167,11 @@ __all__ = [
     "AgenticRAG",       # Agent 自主决定是否检索
     "SimpleVectorStore", # 零依赖向量存储
     "create_rag",       # 一行创建 RAG
+    # Paper III: 类型系统
+    "LamType", "TypeTag", "AgentType", "AgentTypeError",
+    "T_ANY", "T_NONE", "T_STR", "T_INT", "T_FLOAT", "T_BOOL",
+    "T_JSON", "T_TUPLE", "T_UNION",
+    "is_subtype", "check_compose_types", "parse_type_annotation", "infer_type_from_value",
     # 辅助设施
     "Dataset",
     "from_config", "build_agent", "describe_config",

+ 78 - 2
lambdagent/core.py

@@ -13,7 +13,10 @@ import time
 import uuid
 from abc import ABC, abstractmethod
 from dataclasses import dataclass, field
-from typing import Any, Callable, Dict, List, Optional
+from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .types import LamType, AgentType
 
 
 # ============================================================
@@ -92,6 +95,25 @@ class Context:
             duration_ms=duration_ms, model=model, tokens_used=tokens,
         ))
 
+    def fork(self) -> Context:
+        """
+        创建独立的上下文副本(Paper II Proposition 30)。
+
+        用于并行分支: 每个分支有独立的 trace 和 memory,
+        防止 writes(f) ∩ writes(g) ≠ ∅ 造成的竞态条件。
+        bindings 浅拷贝(共享只读变量绑定)。
+        """
+        return Context(
+            bindings=dict(self.bindings),  # 浅拷贝
+            trace=[],                       # 独立 trace
+            memory=dict(self.memory),       # 独立 memory 副本
+            parent=self.parent,
+        )
+
+    def merge_trace(self, other: Context):
+        """合并子上下文的 trace 到当前上下文"""
+        self.trace.extend(other.trace)
+
     def print_trace(self):
         """打印完整的 β-规约链"""
         for i, e in enumerate(self.trace):
@@ -118,6 +140,41 @@ class Term(ABC):
     def __init__(self, name: str = ""):
         self._name = name or self.__class__.__name__
         self._trace_id = uuid.uuid4().hex[:8]
+        # Paper III: 类型标注 (默认 Any → Any)
+        self._input_type: LamType | None = None
+        self._output_type: LamType | None = None
+
+    # ── Paper III: 类型标注属性 ──
+
+    @property
+    def input_type(self) -> LamType:
+        """Agent 的输入类型 (Paper III Definition 3)"""
+        if self._input_type is not None:
+            return self._input_type
+        from .types import T_ANY
+        return T_ANY
+
+    @input_type.setter
+    def input_type(self, t: LamType):
+        self._input_type = t
+
+    @property
+    def output_type(self) -> LamType:
+        """Agent 的输出类型 (Paper III Definition 3)"""
+        if self._output_type is not None:
+            return self._output_type
+        from .types import T_ANY
+        return T_ANY
+
+    @output_type.setter
+    def output_type(self, t: LamType):
+        self._output_type = t
+
+    @property
+    def agent_type(self) -> AgentType:
+        """完整的 Agent 函数类型 τ1 →^ε τ2"""
+        from .types import AgentType
+        return AgentType(self.input_type, self.output_type)
 
     @abstractmethod
     def apply(self, input: Any, ctx: Context) -> Any:
@@ -129,10 +186,29 @@ class Term(ABC):
         return self.apply(input, ctx or Context())
 
     def __rshift__(self, other: Term) -> Term:
-        """语法糖: f >> g = Compose(f, g) = λx. g(f(x))"""
+        """
+        语法糖: f >> g = Compose(f, g) = λx. g(f(x))
+
+        Paper III T-Compose: 检查 output(f) <: input(g)
+        """
         from .primitives import Compose
         if isinstance(self, type) and issubclass(self, Term):
             raise TypeError("Use instances, not classes")
+
+        # Paper III T-Compose: 类型检查 (仅当两端都有非 Any 类型标注时)
+        from .types import T_ANY, is_subtype
+        f_out = self.output_type if hasattr(self, 'stages') else self.output_type
+        g_in = other.input_type
+        if f_out != T_ANY and g_in != T_ANY:
+            if not is_subtype(f_out, g_in):
+                from .types import AgentTypeError
+                raise AgentTypeError(
+                    f"Type mismatch: {self._name} >> {other._name}",
+                    source_type=f_out,
+                    target_type=g_in,
+                    position=0,
+                )
+
         if hasattr(self, 'stages'):  # flatten nested Compose
             return Compose(*self.stages, other)
         return Compose(self, other)

+ 25 - 0
lambdagent/fromconfig/compiler.py

@@ -25,6 +25,11 @@ from typing import Any, Callable, Dict, List, Optional
 from lambdagent.core import Term, Context, LambdagentError
 from lambdagent.primitives import Lam, Compose, Loop, Tool
 from lambdagent.extensions import Par, Route, Memory, Guard
+from lambdagent.types import (
+    LamType, AgentType, AgentTypeError,
+    T_ANY, T_STR, T_JSON,
+    parse_type_annotation, check_compose_types, is_subtype,
+)
 
 from .errors import CompileError, SchemaError, SemanticError
 from .schema import validate_schema
@@ -795,6 +800,8 @@ def _compile_chain(cfg: Dict, overrides: Dict) -> Term:
     """
     type: chain -> Compose(step1, step2, ..., stepN)
     Lambda: lambda x. stepN(...step2(step1(x)))
+
+    Paper III T-Compose: 检查每对相邻步骤的类型兼容性。
     """
     chain_cfg = cfg.get("chain", {})
     steps = chain_cfg.get("steps", [])
@@ -815,6 +822,12 @@ def _compile_chain(cfg: Dict, overrides: Dict) -> Term:
             max_tokens=step_model.get("maxTokens", base_model.get("maxTokens", 1024)),
         )
 
+        # Paper III: 解析类型标注 (inputType / outputType)
+        if "inputType" in step_cfg:
+            step_lam.input_type = parse_type_annotation(step_cfg["inputType"])
+        if "outputType" in step_cfg:
+            step_lam.output_type = parse_type_annotation(step_cfg["outputType"])
+
         # Wrap with Guard if step has guard config
         guard = step_cfg.get("guard")
         if guard:
@@ -822,6 +835,18 @@ def _compile_chain(cfg: Dict, overrides: Dict) -> Term:
 
         stages.append(step_lam)
 
+    # Paper III T-Compose: 静态类型检查 (仅当步骤有类型标注时)
+    agent_types = [s.agent_type for s in stages]
+    has_type_annotations = any(
+        at.input_type != T_ANY or at.output_type != T_ANY
+        for at in agent_types
+    )
+    if has_type_annotations:
+        try:
+            check_compose_types(agent_types)
+        except AgentTypeError as e:
+            raise SemanticError("T001", str(e))
+
     if len(stages) == 1:
         return stages[0]
     return Compose(*stages)

+ 7 - 0
lambdagent/primitives.py

@@ -194,6 +194,10 @@ class Compose(Term):
         name = " >> ".join(s._name for s in stages)
         super().__init__(name)
         self.stages = list(stages)
+        # Paper III: Compose 的类型 = input(first) → output(last)
+        if stages:
+            self._input_type = stages[0]._input_type
+            self._output_type = stages[-1]._output_type
 
     def apply(self, input: Any, ctx: Context | None = None) -> Any:
         ctx = ctx or Context()
@@ -307,6 +311,9 @@ class Pair(Term):
         super().__init__(f"Pair({first._name}, {second._name})")
         self.first = first
         self.second = second
+        # Paper III: Pair 输出类型 = (output(first), output(second))
+        from .types import T_TUPLE
+        self._output_type = T_TUPLE(first.output_type, second.output_type)
 
     def apply(self, input: Any, ctx: Context | None = None) -> tuple:
         ctx = ctx or Context()

+ 433 - 0
lambdagent/tests/test_types.py

@@ -0,0 +1,433 @@
+"""
+Tests for Paper III Type System — T-Compose + Json(S) subtyping.
+
+Tests cover:
+  1. Basic subtype relations (Definition 5)
+  2. Json(S) structural subtyping (width + depth)
+  3. T-Compose type checking (§3.3.3)
+  4. Type annotation parsing (YAML integration)
+  5. Compose >> operator type checking
+"""
+
+import pytest
+from lambdagent.types import (
+    LamType, TypeTag, AgentType, AgentTypeError,
+    T_ANY, T_NONE, T_STR, T_INT, T_FLOAT, T_BOOL, T_JSON, T_TUPLE, T_UNION,
+    is_subtype, check_compose_types, parse_type_annotation, infer_type_from_value,
+)
+from lambdagent.core import Term, Context
+from lambdagent.primitives import Lam, Compose, Tool, Pair
+
+
+# ============================================================
+# 1. Basic Subtype Relations (Paper III Definition 5)
+# ============================================================
+
+class TestBasicSubtyping:
+    """Test basic subtype rules from Definition 5."""
+
+    def test_reflexivity(self):
+        """τ <: τ"""
+        assert is_subtype(T_STR, T_STR)
+        assert is_subtype(T_INT, T_INT)
+        assert is_subtype(T_FLOAT, T_FLOAT)
+        assert is_subtype(T_BOOL, T_BOOL)
+
+    def test_top_type(self):
+        """τ <: ⊤ (Any is top)"""
+        assert is_subtype(T_STR, T_ANY)
+        assert is_subtype(T_INT, T_ANY)
+        assert is_subtype(T_BOOL, T_ANY)
+        assert is_subtype(T_JSON(), T_ANY)
+
+    def test_bottom_type(self):
+        """⊥ <: τ (None is bottom)"""
+        assert is_subtype(T_NONE, T_STR)
+        assert is_subtype(T_NONE, T_INT)
+        assert is_subtype(T_NONE, T_ANY)
+        assert is_subtype(T_NONE, T_JSON())
+
+    def test_numeric_hierarchy(self):
+        """Bool <: Int <: Float"""
+        assert is_subtype(T_BOOL, T_INT)
+        assert is_subtype(T_BOOL, T_FLOAT)
+        assert is_subtype(T_INT, T_FLOAT)
+        # Not the reverse
+        assert not is_subtype(T_FLOAT, T_INT)
+        assert not is_subtype(T_INT, T_BOOL)
+
+    def test_str_json_string(self):
+        """Str <: Json(string)"""
+        json_str = T_JSON({"type": "string"})
+        assert is_subtype(T_STR, json_str)
+        # Str <: Json (untyped)
+        assert is_subtype(T_STR, T_JSON())
+
+    def test_primitive_to_json(self):
+        """Int <: Json(integer), Bool <: Json(boolean)"""
+        assert is_subtype(T_INT, T_JSON({"type": "integer"}))
+        assert is_subtype(T_BOOL, T_JSON({"type": "boolean"}))
+        assert is_subtype(T_FLOAT, T_JSON({"type": "number"}))
+
+    def test_incompatible_types(self):
+        """Str ≮: Int, Int ≮: Str"""
+        assert not is_subtype(T_STR, T_INT)
+        assert not is_subtype(T_INT, T_STR)
+        assert not is_subtype(T_STR, T_BOOL)
+
+
+# ============================================================
+# 2. Json(S) Structural Subtyping
+# ============================================================
+
+class TestJsonSubtyping:
+    """Test Json Schema structural subtyping (width + depth)."""
+
+    def test_json_any(self):
+        """Json(S) <: Json (untyped)"""
+        schema = {"type": "object", "properties": {"x": {"type": "string"}}}
+        assert is_subtype(T_JSON(schema), T_JSON())
+
+    def test_width_subtyping(self):
+        """More fields <: fewer fields (width subtyping)"""
+        sub = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "age": {"type": "integer"},
+                "email": {"type": "string"},  # extra field
+            },
+            "required": ["name", "age"],
+        })
+        sup = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "age": {"type": "integer"},
+            },
+            "required": ["name"],
+        })
+        assert is_subtype(sub, sup)
+
+    def test_missing_required_field(self):
+        """Missing required field → not subtype"""
+        sub = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+            },
+        })
+        sup = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "age": {"type": "integer"},
+            },
+            "required": ["name", "age"],
+        })
+        assert not is_subtype(sub, sup)
+
+    def test_depth_subtyping(self):
+        """Nested field types must be compatible (depth subtyping)"""
+        sub = T_JSON({
+            "type": "object",
+            "properties": {
+                "count": {"type": "integer"},
+            },
+        })
+        sup = T_JSON({
+            "type": "object",
+            "properties": {
+                "count": {"type": "number"},  # integer <: number
+            },
+        })
+        assert is_subtype(sub, sup)
+
+    def test_depth_subtype_failure(self):
+        """Incompatible nested types → not subtype"""
+        sub = T_JSON({
+            "type": "object",
+            "properties": {
+                "count": {"type": "string"},
+            },
+        })
+        sup = T_JSON({
+            "type": "object",
+            "properties": {
+                "count": {"type": "integer"},
+            },
+        })
+        assert not is_subtype(sub, sup)
+
+    def test_array_subtyping(self):
+        """Array items covariance"""
+        sub = T_JSON({
+            "type": "array",
+            "items": {"type": "integer"},
+        })
+        sup = T_JSON({
+            "type": "array",
+            "items": {"type": "number"},
+        })
+        assert is_subtype(sub, sup)
+
+    def test_type_mismatch(self):
+        """Different JSON types → not subtype (except integer/number)"""
+        assert not is_subtype(
+            T_JSON({"type": "string"}),
+            T_JSON({"type": "integer"}),
+        )
+        assert not is_subtype(
+            T_JSON({"type": "object"}),
+            T_JSON({"type": "array"}),
+        )
+
+
+# ============================================================
+# 3. Tuple and Union Types
+# ============================================================
+
+class TestCompoundTypes:
+
+    def test_tuple_covariance(self):
+        """(Int, Str) <: (Float, Str) when Int <: Float"""
+        assert is_subtype(
+            T_TUPLE(T_INT, T_STR),
+            T_TUPLE(T_FLOAT, T_STR),
+        )
+
+    def test_tuple_length_mismatch(self):
+        """Different lengths → not subtype"""
+        assert not is_subtype(
+            T_TUPLE(T_INT),
+            T_TUPLE(T_INT, T_STR),
+        )
+
+    def test_union_introduction(self):
+        """τ <: (τ | σ)"""
+        union = T_UNION(T_STR, T_INT)
+        assert is_subtype(T_STR, union)
+        assert is_subtype(T_INT, union)
+        assert not is_subtype(T_FLOAT, union)
+
+    def test_union_subtype(self):
+        """(τ1 | τ2) <: σ when all members <: σ"""
+        union = T_UNION(T_INT, T_BOOL)
+        assert is_subtype(union, T_FLOAT)  # both Int, Bool <: Float
+
+
+# ============================================================
+# 4. T-Compose Type Checking (Paper III §3.3.3)
+# ============================================================
+
+class TestTCompose:
+
+    def test_compatible_chain(self):
+        """Str → Json(S) >> Json(S) → Str should pass"""
+        json_type = T_JSON({"type": "object", "properties": {"result": {"type": "string"}}})
+        types = [
+            AgentType(T_STR, json_type),
+            AgentType(json_type, T_STR),
+        ]
+        result = check_compose_types(types)
+        assert result.input_type == T_STR
+        assert result.output_type == T_STR
+
+    def test_incompatible_chain(self):
+        """Str → Int >> Str → Str should fail (Int ≮: Str)"""
+        types = [
+            AgentType(T_STR, T_INT),
+            AgentType(T_STR, T_STR),
+        ]
+        with pytest.raises(AgentTypeError) as exc_info:
+            check_compose_types(types)
+        assert "not a subtype" in str(exc_info.value)
+
+    def test_any_type_passes(self):
+        """Any → Any always compatible"""
+        types = [
+            AgentType(T_ANY, T_ANY),
+            AgentType(T_ANY, T_ANY),
+        ]
+        result = check_compose_types(types)
+        assert result.input_type == T_ANY
+
+    def test_subtype_compatible(self):
+        """Int output <: Float input should pass"""
+        types = [
+            AgentType(T_STR, T_INT),
+            AgentType(T_FLOAT, T_STR),
+        ]
+        result = check_compose_types(types)
+        assert result.input_type == T_STR
+        assert result.output_type == T_STR
+
+    def test_json_width_subtype_in_chain(self):
+        """Agent outputting {name, age, email} >> Agent expecting {name, age}"""
+        full = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "age": {"type": "integer"},
+                "email": {"type": "string"},
+            },
+            "required": ["name", "age"],
+        })
+        partial = T_JSON({
+            "type": "object",
+            "properties": {
+                "name": {"type": "string"},
+                "age": {"type": "integer"},
+            },
+            "required": ["name"],
+        })
+        types = [
+            AgentType(T_STR, full),
+            AgentType(partial, T_STR),
+        ]
+        result = check_compose_types(types)
+        assert result.input_type == T_STR
+        assert result.output_type == T_STR
+
+    def test_three_step_chain(self):
+        """A → B >> B → C >> C → D"""
+        types = [
+            AgentType(T_STR, T_INT),
+            AgentType(T_INT, T_FLOAT),
+            AgentType(T_FLOAT, T_STR),
+        ]
+        result = check_compose_types(types)
+        assert result.input_type == T_STR
+        assert result.output_type == T_STR
+
+    def test_three_step_chain_fail_middle(self):
+        """A → B >> C → D fails when B ≮: C"""
+        types = [
+            AgentType(T_STR, T_INT),
+            AgentType(T_STR, T_FLOAT),  # Int ≮: Str
+            AgentType(T_FLOAT, T_STR),
+        ]
+        with pytest.raises(AgentTypeError) as exc_info:
+            check_compose_types(types)
+        assert "step 0 >> step 1" in str(exc_info.value)
+
+
+# ============================================================
+# 5. Type Annotation Parsing
+# ============================================================
+
+class TestTypeAnnotationParsing:
+
+    def test_parse_str(self):
+        assert parse_type_annotation("Str") == T_STR
+        assert parse_type_annotation("string") == T_STR
+
+    def test_parse_int(self):
+        assert parse_type_annotation("Int") == T_INT
+        assert parse_type_annotation("integer") == T_INT
+
+    def test_parse_json_schema(self):
+        schema = {"type": "object", "properties": {"x": {"type": "string"}}}
+        result = parse_type_annotation(schema)
+        assert result.tag == TypeTag.JSON
+        assert result.schema == schema
+
+    def test_parse_any(self):
+        assert parse_type_annotation("Any") == T_ANY
+        assert parse_type_annotation(None) == T_ANY
+
+    def test_parse_json_string(self):
+        result = parse_type_annotation('{"type": "string"}')
+        assert result.tag == TypeTag.JSON
+
+
+# ============================================================
+# 6. Operator Type Checking (>> at construction time)
+# ============================================================
+
+class TestOperatorTypeChecking:
+
+    def test_rshift_with_types_pass(self):
+        """f >> g passes when output(f) <: input(g)"""
+        f = Tool("to_int", lambda x: int(x))
+        f.output_type = T_INT
+        g = Tool("to_str", lambda x: str(x))
+        g.input_type = T_FLOAT  # Int <: Float
+        result = f >> g
+        assert isinstance(result, Compose)
+
+    def test_rshift_with_types_fail(self):
+        """f >> g fails when output(f) ≮: input(g)"""
+        f = Tool("to_int", lambda x: int(x))
+        f.output_type = T_INT
+        g = Tool("to_str", lambda x: str(x))
+        g.input_type = T_STR  # Int ≮: Str
+        with pytest.raises(AgentTypeError):
+            f >> g
+
+    def test_rshift_any_type_no_check(self):
+        """f >> g with Any types should not trigger type check"""
+        f = Tool("a", lambda x: x)
+        g = Tool("b", lambda x: x)
+        result = f >> g  # Both have T_ANY, no error
+        assert isinstance(result, Compose)
+
+    def test_pair_output_type(self):
+        """Pair(f, g) has output type (output(f), output(g))"""
+        f = Tool("a", lambda x: x)
+        f.output_type = T_STR
+        g = Tool("b", lambda x: x)
+        g.output_type = T_INT
+        p = Pair(f, g)
+        assert p.output_type == T_TUPLE(T_STR, T_INT)
+
+
+# ============================================================
+# 7. Type Inference from Values
+# ============================================================
+
+class TestTypeInference:
+
+    def test_infer_str(self):
+        assert infer_type_from_value("hello") == T_STR
+
+    def test_infer_int(self):
+        assert infer_type_from_value(42) == T_INT
+
+    def test_infer_float(self):
+        assert infer_type_from_value(3.14) == T_FLOAT
+
+    def test_infer_bool(self):
+        assert infer_type_from_value(True) == T_BOOL
+
+    def test_infer_dict(self):
+        result = infer_type_from_value({"key": "val"})
+        assert result.tag == TypeTag.JSON
+
+    def test_infer_tuple(self):
+        result = infer_type_from_value(("a", 1))
+        assert result.tag == TypeTag.TUPLE
+
+
+# ============================================================
+# 8. Type Repr
+# ============================================================
+
+class TestTypeRepr:
+
+    def test_basic_repr(self):
+        assert repr(T_STR) == "Str"
+        assert repr(T_INT) == "Int"
+        assert repr(T_ANY) == "Any"
+
+    def test_json_repr(self):
+        t = T_JSON({"type": "object", "properties": {"name": {"type": "string"}}})
+        assert "Json" in repr(t)
+
+    def test_tuple_repr(self):
+        t = T_TUPLE(T_STR, T_INT)
+        assert "(Str, Int)" == repr(t)
+
+    def test_agent_type_repr(self):
+        at = AgentType(T_STR, T_INT, "llm(claude)")
+        assert "Str →^llm(claude) Int" == repr(at)

+ 440 - 0
lambdagent/types.py

@@ -0,0 +1,440 @@
+"""
+lambdagent.types — Paper III 类型与效果系统
+
+实现论文 III 的类型系统:
+  - AgentType: Agent 函数类型 τ1 →^ε τ2
+  - LamType: 基础类型构造 (Str, Int, Bool, Float, Any, Json(S))
+  - Json(S): 复用 JSON Schema 作为结构化类型语言 (Definition 2)
+  - 子类型关系 <: (Definition 5): 宽度/深度子类型
+  - T-Compose 规则: f >> g 要求 output(f) <: input(g) (Paper III §3.3.3)
+
+核心方程:
+    is_subtype(τ1, τ2) = True  ⟺  τ1 <: τ2
+
+依赖图:
+    types.py  ←  effects.py (效果标注)
+              ←  compiler.py (编译时类型检查)
+              ←  core.py (Term.input_type / output_type)
+"""
+
+from __future__ import annotations
+
+import json
+from dataclasses import dataclass, field
+from enum import Enum, auto
+from typing import Any, Dict, FrozenSet, List, Optional, Set, Tuple, Union
+
+
+# ============================================================
+# 基础类型 (Paper III Definition 1)
+# ============================================================
+
+class TypeTag(Enum):
+    """类型标签枚举"""
+    ANY = auto()      # ⊤ — 顶类型,所有类型的超类型
+    NONE = auto()     # ⊥ — 底类型,所有类型的子类型
+    STR = auto()      # 字符串
+    INT = auto()      # 整数
+    FLOAT = auto()    # 浮点数
+    BOOL = auto()     # 布尔
+    JSON = auto()     # Json(S) — JSON Schema 结构化类型
+    TUPLE = auto()    # 元组类型 (Pair 的输出)
+    UNION = auto()    # 联合类型 (Route/If 的输出)
+
+
+@dataclass(frozen=True)
+class LamType:
+    """
+    Lambda Agent 的类型。
+
+    Paper III Definition 1:
+        τ ::= Str | Int | Bool | Float | Any | Json(S) | τ1 × τ2 | τ1 | τ2
+
+    其中 S 是 JSON Schema(Definition 2),
+    复用 JSON Schema 作为结构化类型语言。
+    """
+    tag: TypeTag
+    # Json(S): JSON Schema dict (when tag == JSON)
+    schema: Optional[Dict[str, Any]] = field(default=None, hash=False)
+    # Tuple: element types (when tag == TUPLE)
+    elements: Optional[Tuple[LamType, ...]] = None
+    # Union: member types (when tag == UNION)
+    members: Optional[FrozenSet[LamType]] = None
+
+    def __repr__(self) -> str:
+        if self.tag == TypeTag.ANY:
+            return "Any"
+        elif self.tag == TypeTag.NONE:
+            return "None"
+        elif self.tag == TypeTag.STR:
+            return "Str"
+        elif self.tag == TypeTag.INT:
+            return "Int"
+        elif self.tag == TypeTag.FLOAT:
+            return "Float"
+        elif self.tag == TypeTag.BOOL:
+            return "Bool"
+        elif self.tag == TypeTag.JSON:
+            if self.schema:
+                t = self.schema.get("type", "object")
+                if t == "object":
+                    props = self.schema.get("properties", {})
+                    if props:
+                        fields = ", ".join(f"{k}: {v.get('type', '?')}" for k, v in list(props.items())[:3])
+                        if len(props) > 3:
+                            fields += ", ..."
+                        return f"Json({{{fields}}})"
+                elif t == "array":
+                    items = self.schema.get("items", {})
+                    return f"Json([{items.get('type', '?')}])"
+                return f"Json({t})"
+            return "Json"
+        elif self.tag == TypeTag.TUPLE:
+            if self.elements:
+                inner = ", ".join(str(e) for e in self.elements)
+                return f"({inner})"
+            return "()"
+        elif self.tag == TypeTag.UNION:
+            if self.members:
+                inner = " | ".join(str(m) for m in sorted(self.members, key=str))
+                return f"({inner})"
+            return "Never"
+        return f"LamType({self.tag})"
+
+
+# ============================================================
+# 类型常量(快捷方式)
+# ============================================================
+
+T_ANY = LamType(TypeTag.ANY)
+T_NONE = LamType(TypeTag.NONE)
+T_STR = LamType(TypeTag.STR)
+T_INT = LamType(TypeTag.INT)
+T_FLOAT = LamType(TypeTag.FLOAT)
+T_BOOL = LamType(TypeTag.BOOL)
+
+
+def T_JSON(schema: Dict[str, Any] | None = None) -> LamType:
+    """构造 Json(S) 类型"""
+    return LamType(TypeTag.JSON, schema=schema)
+
+
+def T_TUPLE(*elements: LamType) -> LamType:
+    """构造元组类型"""
+    return LamType(TypeTag.TUPLE, elements=tuple(elements))
+
+
+def T_UNION(*members: LamType) -> LamType:
+    """构造联合类型"""
+    return LamType(TypeTag.UNION, members=frozenset(members))
+
+
+# ============================================================
+# AgentType: Agent 函数类型 (Paper III Definition 3)
+# ============================================================
+
+@dataclass(frozen=True)
+class AgentType:
+    """
+    Agent 函数类型: τ1 →^ε τ2
+
+    Paper III Definition 3:
+        每个 Agent 的类型签名是 input_type →^effect output_type
+
+    effect 在 effects.py 中定义,此处暂用字符串占位。
+    """
+    input_type: LamType
+    output_type: LamType
+    effect: str = "pure"  # 暂用字符串;P0-2 后替换为 Effect 类型
+
+    def __repr__(self) -> str:
+        eff = f"^{self.effect}" if self.effect != "pure" else ""
+        return f"{self.input_type} →{eff} {self.output_type}"
+
+
+# ============================================================
+# 子类型关系 <: (Paper III Definition 5)
+# ============================================================
+
+def is_subtype(sub: LamType, sup: LamType) -> bool:
+    """
+    子类型判断: sub <: sup
+
+    Paper III Definition 5:
+        1. ⊥ <: τ (None 是所有类型的子类型)
+        2. τ <: ⊤ (所有类型是 Any 的子类型)
+        3. τ <: τ (自反性)
+        4. Str <: Json(string) (字符串嵌入 JSON)
+        5. Int <: Float (数值提升)
+        6. Bool <: Int (布尔嵌入整数)
+        7. Json(S1) <: Json(S2) when S1 structurally subtypes S2
+           (宽度子类型: S1 有更多字段 → S1 <: S2)
+           (深度子类型: 对应字段类型 S1.f <: S2.f)
+        8. (τ1, τ2) <: (σ1, σ2) when τ1 <: σ1 ∧ τ2 <: σ2 (元组协变)
+        9. τ <: (τ | σ) (联合类型引入)
+    """
+    # ⊥ <: τ
+    if sub.tag == TypeTag.NONE:
+        return True
+
+    # τ <: ⊤
+    if sup.tag == TypeTag.ANY:
+        return True
+
+    # 自反性
+    if sub == sup:
+        return True
+
+    # τ <: (τ | σ) — 联合类型:sub 是 sup 的某个 member 的子类型
+    if sup.tag == TypeTag.UNION and sup.members:
+        return any(is_subtype(sub, m) for m in sup.members)
+
+    # (τ1 | τ2) <: σ — 联合类型的子类型:所有 member 都是 σ 的子类型
+    if sub.tag == TypeTag.UNION and sub.members:
+        return all(is_subtype(m, sup) for m in sub.members)
+
+    # Bool <: Int <: Float
+    if sub.tag == TypeTag.BOOL and sup.tag == TypeTag.INT:
+        return True
+    if sub.tag == TypeTag.BOOL and sup.tag == TypeTag.FLOAT:
+        return True
+    if sub.tag == TypeTag.INT and sup.tag == TypeTag.FLOAT:
+        return True
+
+    # Str <: Json(string)
+    if sub.tag == TypeTag.STR and sup.tag == TypeTag.JSON:
+        if sup.schema and sup.schema.get("type") == "string":
+            return True
+        # Str <: Json (untyped JSON) — 字符串可以被解析为 JSON
+        if sup.schema is None:
+            return True
+
+    # 基础类型 <: Json(对应类型)
+    _tag_to_json_type = {
+        TypeTag.STR: "string",
+        TypeTag.INT: "integer",
+        TypeTag.FLOAT: "number",
+        TypeTag.BOOL: "boolean",
+    }
+    if sub.tag in _tag_to_json_type and sup.tag == TypeTag.JSON:
+        if sup.schema and sup.schema.get("type") == _tag_to_json_type[sub.tag]:
+            return True
+
+    # Json(S1) <: Json(S2) — 结构子类型
+    if sub.tag == TypeTag.JSON and sup.tag == TypeTag.JSON:
+        return _json_schema_subtype(sub.schema, sup.schema)
+
+    # 元组协变: (τ1, τ2) <: (σ1, σ2)
+    if sub.tag == TypeTag.TUPLE and sup.tag == TypeTag.TUPLE:
+        if sub.elements and sup.elements:
+            if len(sub.elements) != len(sup.elements):
+                return False
+            return all(
+                is_subtype(s, t) for s, t in zip(sub.elements, sup.elements)
+            )
+
+    return False
+
+
+def _json_schema_subtype(
+    sub_schema: Optional[Dict[str, Any]],
+    sup_schema: Optional[Dict[str, Any]],
+) -> bool:
+    """
+    JSON Schema 结构子类型检查。
+
+    Paper III Definition 5 规则 7:
+        Json(S1) <: Json(S2) 当且仅当:
+          - S2 的所有 required 字段在 S1 中都存在
+          - 对应字段类型满足 S1.field <: S2.field (深度子类型)
+          - S1 可以有额外字段 (宽度子类型)
+
+    类似 TypeScript 的结构子类型。
+    """
+    # 无 schema → Any JSON → Json <: Json
+    if sup_schema is None:
+        return True
+    if sub_schema is None:
+        # 未指定的 JSON 不是有具体 schema 的子类型
+        return sup_schema is None
+
+    sub_type = sub_schema.get("type")
+    sup_type = sup_schema.get("type")
+
+    # 类型不同 → 检查 JSON 原始类型的子类型关系
+    if sub_type != sup_type:
+        # integer <: number
+        if sub_type == "integer" and sup_type == "number":
+            return True
+        return False
+
+    # object 子类型: 宽度 + 深度
+    if sup_type == "object":
+        sub_props = sub_schema.get("properties", {})
+        sup_props = sup_schema.get("properties", {})
+        sup_required = set(sup_schema.get("required", []))
+
+        # sup 的所有 required 字段必须在 sub 中存在
+        for req_field in sup_required:
+            if req_field not in sub_props:
+                return False
+
+        # 深度子类型: 公共字段类型兼容
+        for field_name, sup_field_schema in sup_props.items():
+            if field_name in sub_props:
+                if not _json_schema_subtype(sub_props[field_name], sup_field_schema):
+                    return False
+            elif field_name in sup_required:
+                return False
+            # sup 有字段但 sub 没有 + 非 required → OK (宽度子类型的逆方向,
+            # 这里 sub 少字段不影响,因为 sup 不要求该字段)
+
+        return True
+
+    # array 子类型: items 协变
+    if sup_type == "array":
+        sub_items = sub_schema.get("items", {})
+        sup_items = sup_schema.get("items", {})
+        if sub_items and sup_items:
+            return _json_schema_subtype(sub_items, sup_items)
+        return True
+
+    # 基础类型相同 → 子类型
+    return True
+
+
+# ============================================================
+# 类型检查错误
+# ============================================================
+
+class AgentTypeError(Exception):
+    """Agent 类型检查错误 — T-Compose 规则违反"""
+
+    def __init__(self, message: str, source_type: Optional[LamType] = None,
+                 target_type: Optional[LamType] = None, position: int = -1):
+        self.source_type = source_type
+        self.target_type = target_type
+        self.position = position
+        detail = ""
+        if source_type and target_type:
+            detail = f"\n  Output type: {source_type}\n  Input type:  {target_type}"
+            if position >= 0:
+                detail += f"\n  At composition boundary: step {position} >> step {position + 1}"
+        detail += "\n  Rule: Paper III T-Compose: f: A →^ε1 B, g: B' →^ε2 C requires B <: B'"
+        super().__init__(f"{message}{detail}")
+
+
+# ============================================================
+# 类型检查:T-Compose 规则 (Paper III §3.3.3)
+# ============================================================
+
+def check_compose_types(agent_types: List[AgentType]) -> AgentType:
+    """
+    T-Compose 类型检查。
+
+    Paper III §3.3.3:
+        f: A →^ε1 B,  g: B' →^ε2 C,  B <: B'
+        ─────────────────────────────────────────
+              f >> g : A →^(ε1 · ε2) C
+
+    检查链式组合中每对相邻 agent 的类型兼容性。
+    返回整个组合的类型。
+
+    Raises:
+        AgentTypeError: 类型不兼容时抛出
+    """
+    if not agent_types:
+        return AgentType(T_ANY, T_ANY)
+
+    if len(agent_types) == 1:
+        return agent_types[0]
+
+    for i in range(len(agent_types) - 1):
+        f_type = agent_types[i]
+        g_type = agent_types[i + 1]
+
+        # T-Compose: output(f) <: input(g)
+        if not is_subtype(f_type.output_type, g_type.input_type):
+            raise AgentTypeError(
+                f"Type mismatch in pipeline at step {i} >> step {i + 1}: "
+                f"{f_type.output_type} is not a subtype of {g_type.input_type}",
+                source_type=f_type.output_type,
+                target_type=g_type.input_type,
+                position=i,
+            )
+
+    # 组合结果类型: input(first) → output(last)
+    combined_effect = " · ".join(at.effect for at in agent_types if at.effect != "pure")
+    return AgentType(
+        input_type=agent_types[0].input_type,
+        output_type=agent_types[-1].output_type,
+        effect=combined_effect or "pure",
+    )
+
+
+# ============================================================
+# 类型推断辅助
+# ============================================================
+
+def parse_type_annotation(annotation: Any) -> LamType:
+    """
+    从 YAML 配置中的类型标注解析为 LamType。
+
+    支持的格式:
+        "Str"                    → T_STR
+        "Int"                    → T_INT
+        "Float"                  → T_FLOAT
+        "Bool"                   → T_BOOL
+        "Any"                    → T_ANY
+        "Json"                   → T_JSON()
+        {"type": "object", ...}  → T_JSON(schema)
+        {"type": "string"}       → T_JSON({"type": "string"})
+    """
+    if annotation is None:
+        return T_ANY
+
+    if isinstance(annotation, str):
+        _name_map = {
+            "str": T_STR, "string": T_STR,
+            "int": T_INT, "integer": T_INT,
+            "float": T_FLOAT, "number": T_FLOAT,
+            "bool": T_BOOL, "boolean": T_BOOL,
+            "any": T_ANY,
+            "json": T_JSON(),
+        }
+        lower = annotation.lower().strip()
+        if lower in _name_map:
+            return _name_map[lower]
+        # 可能是 JSON Schema 字符串
+        try:
+            parsed = json.loads(annotation)
+            if isinstance(parsed, dict):
+                return T_JSON(parsed)
+        except (json.JSONDecodeError, TypeError):
+            pass
+        return T_ANY
+
+    if isinstance(annotation, dict):
+        # JSON Schema dict
+        return T_JSON(annotation)
+
+    return T_ANY
+
+
+def infer_type_from_value(value: Any) -> LamType:
+    """从运行时值推断类型(用于调试/trace)"""
+    if isinstance(value, str):
+        return T_STR
+    elif isinstance(value, bool):
+        return T_BOOL
+    elif isinstance(value, int):
+        return T_INT
+    elif isinstance(value, float):
+        return T_FLOAT
+    elif isinstance(value, dict):
+        return T_JSON()
+    elif isinstance(value, (tuple, list)):
+        if isinstance(value, tuple):
+            return T_TUPLE(*(infer_type_from_value(v) for v in value))
+        return T_JSON({"type": "array"})
+    return T_ANY