# lambdagent Runtime Spec

> 从 Lambda 项到真实执行：Agent 运行时规格文档
>
> 版本: 1.0 | 前置: FROM_CONFIG_SPEC.md | 理论基础: LDS ≡ Lambda Calculus

---

## 0. 为什么需要运行时

`from_config()` 编译器已经能把 YAML 翻译成 Lambda 项。但 Lambda 项**本身不能执行**——就像 Lambda 演算只定义了项的结构和规约规则，不定义"纸带在哪里"。

运行时（Runtime）就是**那台执行 β-规约的机器**。

```
编译器 (from_config)     运行时 (Runtime)
────────────────         ────────────────
YAML → Term              Term × Input → Result
定义 Lambda 项            执行 β-规约
静态的                   动态的
O(1) 时间                O(n) 时间，n = β-规约步数
不调用 LLM               调用 LLM / MCP / Shell
不产生副作用              读写 Memory、发 HTTP、写日志
```

Lambda 演算的类比：

```
Lambda 演算               lambdagent
─────────────            ─────────────
项 (Term)                 from_config() 的输出
β-规约规则                Runtime.step()
求值策略 (CBV/CBN)       Runtime 的调度策略
环境 (Γ)                  Context + Memory
Oracle 调用               MCP / CLI Tool 调用
规约终止 (Normal Form)   terminate 检测
```

---

## 1. 运行时架构总览

### 1.1 分层架构

```
┌─────────────────────────────────────────────────────┐
│                    用户接口层                         │
│    CLI (run/repl)  │  HTTP (serve)  │  SDK (Python)  │
├─────────────────────────────────────────────────────┤
│                    运行时核心层                       │
│  ┌─────────┐  ┌──────────┐  ┌───────────────────┐  │
│  │ Executor │  │ Scheduler│  │ TerminationOracle │  │
│  │ (β-规约) │  │ (调度)   │  │ (停止判定)         │  │
│  └────┬────┘  └────┬─────┘  └────────┬──────────┘  │
│       │            │                  │              │
│  ┌────▼────────────▼──────────────────▼──────────┐  │
│  │              ReAct Loop Engine                 │  │
│  │   think → parse → route → invoke → observe    │  │
│  └──────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────┤
│                    基础设施层                         │
│  ┌──────────────┐ ┌────────────┐ ┌────────────────┐│
│  │ LLMProvider   │ │ MCP Client │ │ Memory Backend ││
│  │ ┌ClaudeCode─┐ │ │ (HTTP/SSE/ │ │ (Local/Redis)  ││
│  │ │(--resume)  │ │ │  stdio)    │ │                ││
│  │ ├Anthropic──┤ │ └────────────┘ └────────────────┘│
│  │ │(Messages)  │ │ ┌────────────┐ ┌────────────────┐│
│  │ ├OpenAICompat┤ │ │ CLI Agent  │ │ Trace Store    ││
│  │ │(Ollama/..) │ │ │ (subprocess│ │ (JSON/SQLite)  ││
│  │ └───────────┘ │ └────────────┘ └────────────────┘│
│  └──────────────┘                                    │
├─────────────────────────────────────────────────────┤
│                    多智能体 / 协议层                   │
│  Channel  │ SharedMemory │ GroupChat │ Handoff      │
│  AsyncPar │ Skill/Registry │ A2A │ RAG │ Checkpoint │
├─────────────────────────────────────────────────────┤
│                    观测层                            │
│  Context.trace │ Metrics │ Logs │ Events            │
└─────────────────────────────────────────────────────┘
```

### 1.2 核心等式

```
Runtime.run(term, input) =
    let ctx = Context.new() in
    let result = Executor.reduce(term, input, ctx) in        ← β-规约
    (result, ctx.trace)                                       ← 结果 + 追踪

Executor.reduce(term, input, ctx) =
    match term with
    | ConversationLam(prov, h) → prov.chat(h ++ [input])     ← 对话感知 LLM
    | Lam(p, θ)         → LLMAdapter.call(θ, p, input)      ← 无状态 LLM (legacy)
    | Compose(f, g)     → reduce(g, reduce(f, input))        ← 链式规约
    | Loop(body, cond)  → ReActEngine.run(body, cond, input) ← Y 组合子
    | Tool(name, fn)    → ToolInvoker.call(fn, input)        ← 调工具
    | Route(cls, rs)    → ActionParser.parse → dispatch       ← CASE 分发
    | Memory(a, store)  → MemoryBackend.inject → reduce(a)   ← 环境扩展
    | Guard(a, P)       → reduce(a) then validate(P)          ← 类型约束
    | Pair(f, g)        → (reduce(f, input), reduce(g, input))← 并行
    | If(c, t, e)       → if reduce(c) then reduce(t) else reduce(e)
```

---

## 1.3 双引擎架构 (Phase 6.5)

Phase 6.5 引入了统一 Engine 抽象，支持 RecursiveEngine (Python 调用栈) 和 CEKEngine (Agent CEK Machine) 的运行时切换。通过 YAML 配置 `runtime.engine` 字段或 Python API `engine_mode` 参数选择。CEKEngine 提供逐步成本监控、暂停/恢复和循环检测能力。详见 docs/yaml-config.md §4。

---

## 2. Executor: β-规约引擎

### 2.1 职责

Executor 是运行时的核心——它接收一个 Term 和一个 Input，执行 β-规约，返回 Result。

```python
class Executor:
    """
    β-规约引擎。

    Lambda 语义:
        Executor = 求值器 (Evaluator)
        reduce(term, input) = (term input) →β* result

    求值策略: Call-by-Value (严格求值)
        参数先求值，再传入函数体。
        原因: LLM 调用有副作用（API call），不能延迟求值。
    """

    def __init__(self, config: RuntimeConfig):
        self.llm = LLMAdapter(config.llm)
        self.mcp = MCPClient(config.mcp)
        self.memory = MemoryBackend.create(config.memory)
        self.cli_agents = CLIAgentPool(config.cli)
        self.trace = TraceStore(config.trace)
        self.termination = TerminationOracle(config.termination)

    def reduce(self, term: Term, input: Any, ctx: Context) -> Any:
        """
        对 term 执行一步或多步 β-规约。

        这是运行时的核心循环。每个 Term 类型有不同的规约规则。
        """
```

### 2.2 各 Term 类型的规约规则

```python
def reduce(self, term: Term, input: Any, ctx: Context) -> Any:
    match type(term):

        case ConversationLam:
            # β-规约 with 对话记忆 = provider.chat(history ++ [input])
            return self._reduce_conversation_lam(term, input, ctx)

        case Lam:
            # β-规约 = LLM 前向传播 + 自回归解码 (legacy, stateless)
            return self._reduce_lam(term, input, ctx)

        case Compose:
            # f >> g = λx. g(f(x))
            # 从左到右依次规约
            result = input
            for stage in term.stages:
                result = self.reduce(stage, result, ctx)
            return result

        case Loop:
            # Y 组合子 = ReAct 循环
            return self._reduce_loop(term, input, ctx)

        case Tool:
            # 原语调用 = MCP / CLI / Python
            return self._reduce_tool(term, input, ctx)

        case Route:
            # CASE 分发 = 分类 + 路由
            return self._reduce_route(term, input, ctx)

        case Memory:
            # 环境扩展 = 注入记忆 + 执行 + 写回
            return self._reduce_memory(term, input, ctx)

        case Guard:
            # 类型约束 = 执行 + 验证 + 重试
            return self._reduce_guard(term, input, ctx)

        case Pair:
            # Church 对 = 并行执行
            a = self.reduce(term.first, input, ctx)
            b = self.reduce(term.second, input, ctx)
            return (a, b)

        case If:
            # Church 条件 = 求值条件 + 选择分支
            cond_result = self._eval_condition(term.cond, input, ctx)
            if cond_result:
                return self.reduce(term.then_, input, ctx)
            else:
                return self.reduce(term.else_, input, ctx)
```

### 2.3 Lam 规约：LLM 调用

```python
def _reduce_lam(self, lam: Lam, input: Any, ctx: Context) -> Any:
    """
    (λ_D x) →β F_{M,D}(x)

    即: 把 input 喂给 LLM，拿到 output。
    一次 _reduce_lam = 一次 β-规约 = 一次 LLM API 调用。
    """
    t0 = time.time()

    # 构建完整 prompt
    system_prompt = lam.prompt
    user_message = str(input)

    # 注入上下文信息（如果有）
    if ctx.memory:
        user_message = self._inject_context(user_message, ctx)

    # 调用 LLM
    response = self.llm.call(
        model=lam.model,
        system=system_prompt,
        user=user_message,
        temperature=lam.temperature,
        max_tokens=lam.max_tokens,
    )

    duration = (time.time() - t0) * 1000

    # 输出解析
    result = lam.output_parser(response.text)

    # 记录 β-规约
    ctx.log(
        term_name=lam._name,
        term_id=lam._trace_id,
        inp=input,
        out=result,
        duration_ms=duration,
        model=lam.model,
        tokens=response.usage.total_tokens,
    )

    return result
```

### 2.4 ConversationLam 规约：对话感知 LLM 调用

```python
def _reduce_conversation_lam(self, clam: ConversationLam, input: Any, ctx: Context) -> Any:
    """
    (λ_conv x) →β provider(history ++ [x])

    与无状态 Lam 的区别:
        Lam:             每次调用独立 — LLM 只看到当前输入
        ConversationLam: 每次调用累积 — LLM 看到完整对话历史

    ConversationLam.apply(input) 内部流程:
        1. self.messages.append({"role": "user", "content": input})
        2. managed = self._manage_context()  # 上下文窗口管理
        3. response = self.provider.chat(managed)
        4. self.messages.append({"role": "assistant", "content": response})
        5. return output_parser(response)

    上下文管理策略:
        - 始终保留 system message
        - 始终保留最近 N 轮 (keep_recent_turns = 20)
        - 超过 max_history_tokens 时，旧消息被压缩为 "[对话历史摘要]"
        - token 估算: 4 chars ≈ 1 token
    """
    t0 = time.time()
    result = clam.apply(input, ctx)
    duration = (time.time() - t0) * 1000
    ctx.log(clam._name, clam._trace_id, input, result, duration, clam.model)
    return result
```

#### ConversationLam 的 Lambda 语义

```
ConversationLam(provider, system_prompt) =
    λx. let h' = h ++ [("user", x)] in
        let managed = window(h', max_tokens) in
        let response = provider.chat(managed) in
        let h'' = h' ++ [("assistant", response)] in
        (response, h'')

其中 window(h, n) = if tokens(h) <= n then h
                     else [system] ++ summarize(old(h)) ++ recent(h)
```

这消除了 ReAct 循环中的"失忆"问题: LLM 在每一步都能看到完整对话，
而不是只看到一个通过字符串拼接压缩的状态。

### 2.5 Provider 抽象层

Provider 是 LLM 调用的最底层抽象——只负责消息传输，不负责历史管理。

```python
class LLMProvider(ABC):
    """
    统一的 LLM Provider 接口。

    Lambda 语义:
        LLMProvider = λ messages. LLM_response
        所有 provider 实现同一个 contract: messages in, text out.

    接口:
        chat(messages: list[dict]) -> str

    其中 messages = [
        {"role": "system", "content": "..."},
        {"role": "user",   "content": "..."},
        {"role": "assistant", "content": "..."},
        ...
    ]
    """

    @abstractmethod
    def chat(self, messages: List[Dict[str, str]]) -> str: ...

    @property
    def model_name(self) -> str: ...

    @property
    def context_window(self) -> int: ...
```

#### 已实现的 Provider

| Provider | 传输方式 | 会话持久化 | 适用场景 |
|---|---|---|---|
| `ClaudeCodeProvider` | subprocess (claude CLI) | `--resume <session_id>` (原生) | 需要完整上下文记忆的复杂 ReAct 任务 |
| `AnthropicProvider` | HTTP (Messages API) | 由 ConversationLam 管理 | 标准 Anthropic API 调用 |
| `OpenAICompatProvider` | HTTP (Chat Completions) | 由 ConversationLam 管理 | OpenAI / Ollama / DashScope / DeepSeek / Moonshot / Zhipu |

#### ConversationLam 与 Provider 的职责分离

```
ConversationLam (对话层)          LLMProvider (传输层)
─────────────────────            ─────────────────────
管理 messages 列表                只关心 messages -> str
上下文窗口控制                    不知道历史
摘要压缩旧消息                    不知道 token 预算
记录 assistant 回复               不记录任何东西
输出解析                          不解析输出
```

### 2.6 Session 持久化机制

不同 provider 的会话持久化方式不同:

#### ClaudeCodeProvider: 原生 Session

```
第 1 次调用:
    claude -p "input" --system-prompt "..." --output-format json
    → 返回 {session_id: "abc123", result: "..."}
    → 保存 self._session_id = "abc123"

第 2+ 次调用:
    claude -p "new input" --resume abc123 --output-format text
    → Claude CLI 自动加载完整对话历史
    → 只需发送增量内容 (latest observation)

reset_session():
    self._session_id = None
    → 下次调用创建全新会话
```

这是最高效的模式: 后续 ReAct 步骤只发送最新的工具观察结果，
Claude Code CLI 端自动保持完整上下文。

#### AnthropicProvider / OpenAICompatProvider: ConversationLam 管理

```
每次调用:
    ConversationLam 将 full messages array 传给 provider.chat()
    Provider 将 messages 转发给 HTTP API
    API 端无状态 — 每次都是独立请求

上下���管理:
    ConversationLam._manage_context() 在每次调用前检查 token 预算
    超出时进行 sliding window 压缩
```

#### session 检测在 ReAct 中的作用

```python
# react_engine 的 _build_prompt 根据 think 的类型调整行为:
# ConversationLam 暴露 _session_id property (来自底层 provider)

if hasattr(self.think, '_session_id') and self.think._session_id:
    # Session mode: 只发送增量 (observation)
    # Provider 已有完整上下文
else:
    # Stateless mode: 发送完整状态
    # ConversationLam 负责历史管理
```

---

## 3. ReAct Loop Engine: Y 组合子的真实执行

### 3.1 为什么 ReAct 需要单独的引擎

ReAct 不是简单的 `for step in range(n): body(result)`。它是一个**有内部结构的 Y 组合子展开**，每一步包含 7 个子阶段：

```
Y 组合子的一次展开 (一步 β-规约) =

    think    →    parse    →    route    →    invoke    →    observe    →    update    →    check
    ─────         ─────         ─────         ──────         ───────         ──────         ─────
    LLM           提取           选择           执行            格式化          写回            终止
    推理          动作           工具           工具            观察结果        记忆            判定
```

### 3.2 ReActEngine 完整规格

```python
class ReActEngine:
    """
    ReAct 循环引擎 = Y 组合子的运行时实现。

    Lambda 语义:
        react = Y_n(λself. λstate.
            let (thought, action) = think_and_parse(state) in
            IF (action = terminate)
                THEN extract_answer(thought)           ← base case
                ELSE let obs = invoke(action) in
                     self(state ⊕ format(thought, obs)) ← 递归
        )

    每一步展开 = 7 个子阶段:
        1. THINK:    LLM 推理 (β-规约)
        2. PARSE:    提取结构化动作
        3. ROUTE:    选择工具 (CASE)
        4. INVOKE:   执行工具 (β-规约)
        5. OBSERVE:  格式化观察结果
        6. UPDATE:   写回记忆 (Γ 更新)
        7. CHECK:    终止判定 (base case?)
    """

    def __init__(
        self,
        think: ConversationLam | Lam,    # 推理 Agent (优先 ConversationLam)
        tools: Dict[str, Tool],          # 工具集（含 terminate）
        action_parser: ActionParser,     # 动作解析器
        memory: MemoryBackend,           # 记忆后端
        termination: TerminationOracle,  # 终止判定器
        max_steps: int,                  # Y 组合子最大展开次数
        tool_timeout: int,               # 工具超时(秒)
        observation_enabled: bool,       # 是否追加观察到上下文
    ):
        ...

    def run(self, input: str, ctx: Context) -> str:
        """
        执行完整的 ReAct 循环。

        等价于: Y_n(react_body)(input)
        """
        state = input
        final_answer = None

        for step in range(self.max_steps):
            result = self._step(state, step, ctx)

            if result.terminated:
                final_answer = result.answer
                break

            state = result.next_state

        # 如果 max_steps 用尽仍未终止
        if final_answer is None:
            final_answer = self._force_terminate(state, ctx)

        return final_answer
```

### 3.3 单步展开的 7 个子阶段

```python
@dataclass
class StepResult:
    """一步 Y 组合子展开的结果"""
    terminated: bool           # 是否到达 base case
    answer: Optional[str]      # terminated=True 时的最终答案
    next_state: Optional[str]  # terminated=False 时的下一步输入
    thought: str               # LLM 的推理文本
    action: Optional[Action]   # 解析出的动作
    observation: Optional[str] # 工具执行的观察结果
    step: int                  # 当前步数

@dataclass
class Action:
    """LLM 输出中提取的结构化动作"""
    tool: str                  # 工具名
    input: Dict[str, Any]      # 工具参数
    thought: str               # 推理过程
    raw: str                   # 原始 LLM 输出

def _step(self, state: str, step: int, ctx: Context) -> StepResult:
    """
    ReAct 的一步 = Y 组合子的一次展开。

    Phase 1-7 严格按顺序执行。
    """

    # ═══════════════════════════════════════════
    # Phase 1: THINK — LLM 推理 (β-规约)
    # ═══════════════════════════════════════════
    #
    # Lambda: let thought = think(state) in ...
    # 这是最核心的 β-规约：把当前状态喂给 LLM，拿到推理结果。

    prompt_with_context = self._build_prompt(state, step)
    thought = self.think(prompt_with_context, ctx)

    # ═══════════════════════════════════════════
    # Phase 2: PARSE — 提取结构化动作
    # ═══════════════════════════════════════════
    #
    # 从 LLM 的自由文本输出中提取结构化的 Action。
    # 支持多种格式：JSON / XML / 关键词。

    try:
        action = self.action_parser.parse(thought)
    except ParseError as e:
        # 解析失败 → 告诉 LLM 格式错误，让它重试
        observation = f"[FORMAT_ERROR] {e}. Please output a valid action."
        next_state = self._append_observation(state, thought, observation)
        ctx.log("parse_error", "", thought, observation, 0)
        return StepResult(
            terminated=False, answer=None,
            next_state=next_state,
            thought=thought, action=None, observation=observation,
            step=step,
        )

    # ═══════════════════════════════════════════
    # Phase 3: ROUTE — 选择工具 (CASE 分发)
    # ═══════════════════════════════════════════
    #
    # Lambda: CASE action.tool [
    #     ("sum",       Tool_MCP),
    #     ("improve",   Tool_MCP),
    #     ("terminate", λx.x),
    # ]

    if action.tool not in self.tools:
        observation = f"[ROUTE_ERROR] Unknown tool: {action.tool}. Available: {list(self.tools.keys())}"
        next_state = self._append_observation(state, thought, observation)
        ctx.log("route_error", "", action.tool, observation, 0)
        return StepResult(
            terminated=False, answer=None,
            next_state=next_state,
            thought=thought, action=action, observation=observation,
            step=step,
        )

    tool = self.tools[action.tool]

    # ═══════════════════════════════════════════
    # Phase 4: INVOKE — 执行工具 (β-规约)
    # ═══════════════════════════════════════════
    #
    # Lambda: let obs = tool(action.input) in ...
    #
    # 三种情况:
    # a) terminate → λx.x → 直接返回 thought (base case)
    # b) MCP Tool → HTTP 调用 MCP 端点
    # c) CLI Tool → subprocess 调用

    # 4a. Base Case: terminate = λx.x
    if action.tool == "terminate":
        answer = self._extract_final_answer(thought, action)
        ctx.log("terminate", "", state, answer, 0)
        return StepResult(
            terminated=True, answer=answer,
            next_state=None,
            thought=thought, action=action, observation=None,
            step=step,
        )

    # 4b/4c. 工具调用
    t0 = time.time()
    try:
        tool_result = self._invoke_with_timeout(
            tool, action.input, self.tool_timeout
        )
        duration = (time.time() - t0) * 1000
        ctx.log(f"Tool:{action.tool}", "", action.input, tool_result, duration)
    except TimeoutError:
        tool_result = f"[TIMEOUT] Tool '{action.tool}' exceeded {self.tool_timeout}s"
        ctx.log(f"Tool:{action.tool}", "", action.input, tool_result, 0)
    except Exception as e:
        tool_result = f"[TOOL_ERROR] {e}"
        ctx.log(f"Tool:{action.tool}", "", action.input, tool_result, 0)

    # ═══════════════════════════════════════════
    # Phase 5: OBSERVE — 格式化观察结果
    # ═══════════════════════════════════════════
    #
    # 将工具的原始输出格式化为 LLM 能理解的观察文本。
    # 截断过长的输出（防止 context overflow）。

    observation = self._format_observation(action.tool, tool_result)

    # ═══════════════════════════════════════════
    # Phase 6: UPDATE — 写回记忆 (Γ 更新)
    # ═══════════════════════════════════════════
    #
    # Lambda: Γ' = Γ ∪ {step_n: (thought, observation)}
    # 自动将本步的关键信息写入 Memory。

    self.memory.auto_save(
        key=f"step_{step}",
        thought=thought,
        action=action.tool,
        observation=observation,
    )

    # ═══════════════════════════════════════════
    # Phase 7: CHECK — 终止判定 (base case 检测)
    # ═══════════════════════════════════════════
    #
    # 除了显式调用 terminate 外，还有隐式终止条件:
    # - LLM 输出中包含 "Final Answer:" 等信号
    # - 工具返回了终止标记
    # - 达到步数上限

    if self.termination.should_stop(thought, observation, step):
        answer = self._extract_final_answer(thought, action)
        return StepResult(
            terminated=True, answer=answer,
            next_state=None,
            thought=thought, action=action, observation=observation,
            step=step,
        )

    # ═══════════════════════════════════════════
    # 准备下一步输入 (递归: self(state ⊕ obs))
    # ═══════════════════════════════════════════

    next_state = self._append_observation(state, thought, observation)

    return StepResult(
        terminated=False, answer=None,
        next_state=next_state,
        thought=thought, action=action, observation=observation,
        step=step,
    )
```

### 3.4 Prompt 构建 (`_build_prompt`)

```python
def _build_prompt(self, state: str, step: int) -> str:
    """
    构建送给 LLM 的完整 prompt。

    结构:
        [System]         ← systemPrompt (from YAML)
        [Memory]         ← 持久记忆 (from Memory Backend)
        [Conversation]   ← 历史 think/observe 对
        [Step Info]      ← 当前步数 / 剩余步数
        [Available Tools]← 可用工具列表 + 调用格式说明
        [User Input]     ← 原始用户输入 (第一步) 或上一步状态

    Lambda 语义:
        prompt = Γ(memory) ⊕ Γ(tools) ⊕ state ⊕ step_info
    """

    parts = []

    # 记忆注入
    memory_items = self.memory.read_recent(n=self.memory_size)
    if memory_items:
        parts.append("[Memory Context]")
        for key, value, age in memory_items:
            parts.append(f"  - {key}: {value} ({age})")
        parts.append("")

    # 工具说明
    parts.append("[Available Tools]")
    parts.append("Call a tool by outputting JSON: {\"action\": \"tool_name\", \"input\": {...}}")
    parts.append("To finish, call: {\"action\": \"terminate\", \"answer\": \"your final answer\"}")
    parts.append("")
    for name, tool in self.tools.items():
        if name == "terminate":
            parts.append(f"  - terminate: Signal task completion (λx.x)")
        else:
            schema = getattr(tool, 'schema', None)
            desc = schema.get('description', '') if schema else ''
            parts.append(f"  - {name}: {desc}")
    parts.append("")

    # 步数信息
    remaining = self.max_steps - step - 1
    parts.append(f"[Step {step + 1}/{self.max_steps}, remaining: {remaining}]")
    if remaining <= 3:
        parts.append("⚠ Running low on steps. Consider wrapping up or calling terminate.")
    parts.append("")

    # 当前状态
    parts.append(state)

    return "\n".join(parts)
```

---

## 4. ActionParser: 从 LLM 输出提取结构化动作

### 4.1 职责

LLM 输出的是自由文本。ActionParser 负责从中提取结构化的 `Action(tool, input, thought)`。

### 4.2 解析优先级

```
优先级 1: JSON 块
    LLM 输出: "Let me calculate... ```json\n{\"action\": \"sum\", \"input\": {\"numbers\": [1,2,3]}}\n```"
    提取: Action(tool="sum", input={"numbers": [1,2,3]})

优先级 2: 内联 JSON
    LLM 输出: "I'll use the sum tool. {\"action\": \"sum\", \"input\": {\"numbers\": [1,2,3]}}"
    提取: 同上

优先级 3: XML 标签
    LLM 输出: "<action>sum</action><input>{\"numbers\": [1,2,3]}</input>"
    提取: 同上

优先级 4: 关键词 + 启发式
    LLM 输出: "I need to use the sum tool to calculate 1+2+3"
    提取: Action(tool="sum", input={"query": "calculate 1+2+3"})
    注意: 这是兜底策略，精度较低

优先级 5: 无动作 (隐式 terminate)
    LLM 输出: "The answer is 42. Task complete."
    提取: Action(tool="terminate", input={"answer": "42"})
```

### 4.3 接口规格

```python
class ActionParser:
    """
    从 LLM 自由文本输出中提取结构化动作。

    Lambda 语义:
        parse: String → Action
        parse(thought) = (tool_name, params, reasoning)

    这对应 Lambda 演算中的"模式匹配":
        CASE thought [
            (json_pattern,    extract_json),
            (xml_pattern,     extract_xml),
            (keyword_pattern, extract_keyword),
            (_,               implicit_terminate),
        ]
    """

    def __init__(self, tool_names: List[str], tool_schemas: Dict[str, dict] = None):
        self.tool_names = tool_names
        self.tool_schemas = tool_schemas or {}

    def parse(self, llm_output: str) -> Action:
        """
        解析 LLM 输出为 Action。

        返回: Action(tool, input, thought, raw)
        抛出: ParseError（如果无法解析且不满足隐式终止条件）
        """

    def _try_json_block(self, text: str) -> Optional[Action]:
        """尝试提取 ```json ... ``` 块"""

    def _try_inline_json(self, text: str) -> Optional[Action]:
        """尝试提取行内 JSON 对象"""

    def _try_xml(self, text: str) -> Optional[Action]:
        """尝试提取 XML 标签"""

    def _try_keyword(self, text: str) -> Optional[Action]:
        """启发式关键词匹配"""

    def _try_implicit_terminate(self, text: str) -> Optional[Action]:
        """检测隐式终止信号"""
```

### 4.4 Action 校验

```python
def validate_action(self, action: Action) -> Action:
    """
    校验 Action 的工具名和参数。

    1. 工具名必须在 tool_names 中
    2. 如果有 schema，参数必须符合 schema
    3. 参数类型必须正确

    Lambda 语义:
        validate: Action → Action | ⊥
        这是 Guard(parse, P) 的应用: 依赖类型约束
    """
    if action.tool not in self.tool_names:
        raise ParseError(
            f"Unknown tool: '{action.tool}'. "
            f"Available: {self.tool_names}"
        )

    if action.tool in self.tool_schemas:
        schema = self.tool_schemas[action.tool]
        # JSON Schema 校验
        self._validate_params(action.input, schema)

    return action
```

---

## 5. TerminationOracle: 何时停下

### 5.1 职责

判定 Y 组合子是否到达 base case。在 Lambda 演算中，base case 是"不再调用 self 的分支"。在 Agent 中，对应"不再需要新工具调用"的时刻。

### 5.2 终止条件层次

```python
class TerminationOracle:
    """
    终止判定器 = Y 组合子的 base case 检测。

    Lambda 语义:
        should_stop = λ(thought, obs, step).
            IF (action = terminate) THEN TRUE           ← 显式 base case
            ELSE IF (implicit_signal(thought)) THEN TRUE ← 隐式 base case
            ELSE IF (step >= max) THEN TRUE              ← 强制截断
            ELSE FALSE                                    ← 继续递归
    """

    def should_stop(self, thought: str, observation: str, step: int) -> bool:
        """
        三层终止检测:

        Layer 1: 显式终止
            Agent 调用了 terminate 工具。
            这是 λx.x — 恒等函数 — Y 组合子的标准 base case。

        Layer 2: 隐式终止信号
            Agent 输出中包含终止信号，但没有显式调用 terminate。
            例如: "Final Answer: 42", "Task complete.", "I'm done."

        Layer 3: 强制截断
            达到 maxSteps。
            Lambda 对应: Y_n 的有界性——最多展开 n 次。
        """

        # Layer 1: 显式 (已在 Phase 4 处理，这里是备用)
        if "terminate" in thought.lower() and "action" in thought.lower():
            return True

        # Layer 2: 隐式信号
        implicit_signals = [
            "final answer:",
            "task complete",
            "task is done",
            "i have completed",
            "here is the result:",
            "in conclusion,",
        ]
        thought_lower = thought.lower()
        for signal in implicit_signals:
            if signal in thought_lower:
                return True

        # Layer 3: 在 caller 处检测 (step >= max_steps)
        # 这里不检查，由 ReActEngine.run() 的 for 循环处理

        return False
```

### 5.3 强制终止时的答案提取

```python
def _force_terminate(self, state: str, ctx: Context) -> str:
    """
    maxSteps 用尽时的强制终止。

    Lambda: Y_n(g) 展开 n 次后截断 → 返回当前项（非 normal form）

    策略:
        1. 让 LLM 从当前状态中提取最终答案
        2. 如果 LLM 也失败，返回最后一步的 thought

    这是"有界递归"的代价——可能返回不完整的结果。
    """
    # 用一次额外的 LLM 调用提取答案
    extractor = Lam(
        "force_extract",
        "The agent ran out of steps. Extract the best answer from the conversation so far. "
        "If no clear answer, summarize what was accomplished.",
        max_tokens=512,
    )
    return extractor(state, ctx)
```

---

## 6. LLM Provider 层: 多模型统一接口

### 6.1 新架构: LLMProvider (v2)

v2 采用 `LLMProvider` 接口替代旧的 `LLMAdapter`。核心变化:
- 接口从 `call(model, system, user)` 变为 `chat(messages: list[dict]) -> str`
- 会话管理由 `ConversationLam` 负责，Provider 只负责传输
- Provider 通过 `_create_provider()` 工厂函数创建，不通过注册表

```python
class LLMProvider(ABC):
    """
    统一的 LLM Provider 接口 (v2)。

    Lambda 语义:
        LLMProvider = λ messages. response_text

    与旧 LLMAdapter 的区别:
        LLMAdapter: call(model, system, user) → LLMResponse  (model-centric)
        LLMProvider: chat(messages) → str                     (conversation-centric)

    已实现的 Provider:
        - ClaudeCodeProvider  (subprocess, session persistence)
        - AnthropicProvider   (HTTP, Anthropic Messages API)
        - OpenAICompatProvider(HTTP, OpenAI Chat Completions API)
    """

    def __init__(self, config: ProviderConfig): ...

    @abstractmethod
    def chat(self, messages: List[Dict[str, str]]) -> str:
        """
        发送 messages 给 LLM，返回 response 文本。

        Args:
            messages: [{"role": "system/user/assistant", "content": "..."}]

        Returns:
            assistant 的 response 文本。

        Raises:
            ProviderError: API/连接失败。
        """
        ...

    @property
    def model_name(self) -> str: ...

    @property
    def context_window(self) -> int: ...

@dataclass
class ProviderConfig:
    model: str = ""
    temperature: float = 0.3
    max_tokens: int = 4096
    timeout: int = 600
    context_window: int = 200000
    extra: Dict[str, Any] = field(default_factory=dict)
```

### 6.2 Provider 工厂: _create_provider

```python
def _create_provider(model_cfg: Dict) -> (LLMProvider, bool):
    """
    工厂函数: 根据 YAML model 配置创建 Provider 实例。

    路由逻辑 (CASE dispatch):
        model.provider == "claude-code"  → ClaudeCodeProvider
        model.provider == "anthropic"    → AnthropicProvider
        model.provider ∈ {"openai", "ollama", "dashscope",
                          "deepseek", "moonshot", "zhipu"}
                                         → OpenAICompatProvider

    返回 (provider, use_conversation):
        use_conversation 由 model.conversation 字段控制 (默认 true)
        true  → 编译器将 provider 包装为 ConversationLam
        false → 编译器使用旧的 Lam (无状态)
    """
```

### 6.3 ClaudeCodeProvider: Session 持久化

```python
class ClaudeCodeProvider(LLMProvider):
    """
    基于 Claude Code CLI 的 Provider。

    传输方式: subprocess 调用 `claude` CLI
    会话持久化: --resume <session_id>

    chat() 流程:
        第 1 次:
            1. claude -p "input" --system-prompt "..." --output-format json
            2. 解析 JSON: {session_id, result}
            3. 保存 self._session_id
            4. 返回 result

        第 2+ 次:
            1. 提取 messages 中最新的 user message
            2. claude -p "latest" --resume <session_id> --output-format text
            3. 返回 stdout

    Lambda 语义:
        chat₁ = λ msgs. let (sid, r) = claude_new(msgs) in (r, sid)
        chatₙ = λ msgs. claude_resume(sid, last(msgs))
    """

    def chat(self, messages: List[Dict[str, str]]) -> str:
        if self._session_id:
            return self._call_resume(messages)  # 只发最新消息
        else:
            return self._call_new(messages)     # 创建 session

    def reset_session(self):
        """Reset session — 下次调用创建新会话。"""
        self._session_id = None
```

### 6.4 OpenAICompatProvider: 无状态 HTTP

```python
class OpenAICompatProvider(LLMProvider):
    """
    兼容 OpenAI Chat Completions API 的 Provider。

    支持: OpenAI / Ollama / DashScope / DeepSeek / Moonshot / Zhipu

    chat() 流程:
        1. 根据 provider_name 确定 base_url 和 api_key
        2. POST /v1/chat/completions {"model": ..., "messages": [...]}
        3. 解析 response.choices[0].message.content
        4. 返回文本

    无状态: 每次调用都是独立的 HTTP 请求，
    由 ConversationLam 在调用前组装完整 messages 数组。
    """
```

---

## 7. MCP Client: 工具调用的真实实现

### 7.1 接口

```python
class MCPClient:
    """
    MCP 协议客户端。

    Lambda 语义:
        MCPClient = λ(server, tool, input). HTTP_POST(url, {tool, input})

    对应 YAML:
        app.mcp.custom.nodes.<server>.url
        app.mcp.custom.nodes.<server>.endpoint
        app.mcp.custom.nodes.<server>.headers
    """

    def __init__(self, nodes: Dict[str, MCPNodeConfig]):
        self.nodes = nodes   # server_name → config
        self.clients = {}    # server_name → httpx.AsyncClient

    async def invoke(
        self,
        server: str,
        tool: str,
        input: Dict[str, Any],
        timeout: int = 30,
    ) -> str:
        """
        调用 MCP 工具。

        HTTP 请求:
            POST {url}{endpoint}
            Headers: {Authorization: token, Content-Type: application/json}
            Body: {"jsonrpc": "2.0", "method": "tools/call",
                   "params": {"name": tool, "arguments": input}}

        返回: 工具执行结果（字符串）
        异常: MCPError (连接失败/超时/4xx/5xx)
        """

    async def discover(self, server: str) -> List[ToolSchema]:
        """
        发现 MCP 服务器上的可用工具。

        HTTP 请求:
            POST {url}{endpoint}
            Body: {"jsonrpc": "2.0", "method": "tools/list"}

        返回: [ToolSchema(name, description, input_schema), ...]
        """

@dataclass
class MCPNodeConfig:
    url: str
    endpoint: str
    headers: Dict[str, str]
    timeout: int = 30
    retry: int = 0

@dataclass
class ToolSchema:
    name: str
    description: str
    input_schema: Dict     # JSON Schema
```

### 7.2 连接管理

```python
class MCPConnectionPool:
    """
    MCP 连接池。

    每个 MCP server 维护一个持久连接。
    支持:
        - HTTP (请求/响应)
        - SSE (服务端推送，用于流式工具结果)
        - WebSocket (双向，用于长时间运行的工具)

    Lambda 语义:
        Pool = Memory(connections, {server → client})
        即: 连接池是一种环境扩展——把已建立的连接存入 Γ
    """
```

---

## 8. Memory Backend: 环境 Γ 的持久化

### 8.1 接口

```python
class MemoryBackend(ABC):
    """
    记忆后端抽象接口。

    Lambda 语义:
        MemoryBackend = Γ 的存储实现
        read()  = 查找变量绑定 (Γ.lookup)
        write() = 环境扩展 (Γ' = Γ ∪ {k: v})
        evict() = 绑定回收 (Γ' = Γ \ expired)
    """

    @abstractmethod
    def read_recent(self, n: int) -> List[Tuple[str, Any, str]]:
        """读取最近 n 条记忆。返回 [(key, value, age_str), ...]"""

    @abstractmethod
    def read(self, key: str) -> Optional[Any]:
        """读取指定 key"""

    @abstractmethod
    def write(self, key: str, value: Any) -> None:
        """写入（自动 TTL 和 LRU 淘汰）"""

    @abstractmethod
    def auto_save(self, key: str, thought: str, action: str, observation: str) -> None:
        """自动从一步执行结果中提取并保存关键信息"""

    @abstractmethod
    def clear(self) -> None:
        """清空全部记忆"""

    @staticmethod
    def create(config: MemoryConfig) -> "MemoryBackend":
        """工厂方法: 根据配置创建对应后端"""
        match config.strategy:
            case "local":  return LocalMemory(config.size, config.ttl)
            case "redis":  return RedisMemory(config.size, config.ttl, config.redis_url)
            case "sqlite": return SQLiteMemory(config.size, config.ttl, config.db_path)
            case _:        return LocalMemory(config.size, config.ttl)
```

### 8.2 LocalMemory (默认)

```python
class LocalMemory(MemoryBackend):
    """
    进程内记忆后端。

    数据结构: OrderedDict (保持插入顺序，支持 LRU 淘汰)
    TTL: 通过时间戳检查
    Size: 通过 OrderedDict.popitem(last=False) 淘汰最旧条目

    Lambda 语义: Γ 存在 Python 进程的堆内存中。进程退出则 Γ 丢失。
    """
```

### 8.3 RedisMemory (生产)

```python
class RedisMemory(MemoryBackend):
    """
    Redis 记忆后端。

    数据结构: Redis Sorted Set (score = timestamp，自动按时间排序)
    TTL: Redis 原生 EXPIRE
    Size: ZREMRANGEBYRANK 淘汰最旧条目
    Scope: key prefix = agent_id + session_id

    Lambda 语义: Γ 存在 Redis 中。跨进程、跨机器共享。
    """
```

### 8.4 auto_save: 自动信息提取

```python
def auto_save(self, key: str, thought: str, action: str, observation: str):
    """
    从一步执行结果中自动提取并保存关键信息。

    策略 1 (简单): 保存整个 (thought, action, observation) 三元组
    策略 2 (摘要): 用 LLM 提取关键事实再保存
    策略 3 (结构化): 从 observation 中正则提取 key=value 对

    默认用策略 1 (最可靠)，可通过配置切换。
    """
    summary = f"[{action}] {observation[:200]}"
    self.write(key, summary)
```

---

## 9. Trace Store: β-规约追踪的持久化

### 9.1 追踪数据模型

```python
@dataclass
class TraceRecord:
    """一条 β-规约记录 (比 TraceEntry 更完整)"""
    # 基础
    step: int
    term_name: str
    term_type: str        # "Lam" | "Tool" | "Route" | "Loop" | ...
    duration_ms: float
    timestamp: float

    # 输入输出
    input: str
    output: str
    input_tokens: int
    output_tokens: int

    # ReAct 专用
    thought: Optional[str]
    action: Optional[str]
    action_input: Optional[Dict]
    observation: Optional[str]
    terminated: bool

    # 模型
    model: str
    temperature: float

    # 错误
    error: Optional[str]
```

### 9.2 接口

```python
class TraceStore:
    """
    β-规约追踪存储。

    Lambda 语义:
        TraceStore = List[β-reduction]
        每次 reduce 调用追加一条记录。
        最终的 trace = 完整的规约链。
    """

    def append(self, record: TraceRecord): ...
    def get_all(self) -> List[TraceRecord]: ...
    def get_step(self, n: int) -> TraceRecord: ...
    def to_json(self) -> str: ...
    def to_timeline(self) -> str: ...
    def stats(self) -> TraceStats: ...

@dataclass
class TraceStats:
    total_steps: int
    total_time_ms: float
    total_tokens: int
    tool_calls: int
    llm_calls: int
    errors: int
    terminated_by: str   # "terminate" | "implicit" | "max_steps"
```

---

## 10. RuntimeConfig: 运行时配置

### 10.1 从 YAML 构建

```python
@dataclass
class RuntimeConfig:
    """运行时配置，从 YAML + CLI 参数 + 环境变量合并而来"""

    # LLM
    llm: LLMConfig

    # MCP
    mcp: Dict[str, MCPNodeConfig]

    # Memory
    memory: MemoryConfig

    # CLI Agents
    cli: Dict[str, CLIAgentConfig]

    # Termination
    termination: TerminationConfig

    # Trace
    trace: TraceConfig

    # ReAct
    react: ReActConfig

    @staticmethod
    def from_yaml(path: str, **overrides) -> "RuntimeConfig":
        """
        从 YAML 配置文件 + CLI 覆盖 + 环境变量构建运行时配置。

        优先级:
            CLI 参数 > 环境变量 > YAML 配置 > 默认值
        """

@dataclass
class LLMConfig:
    provider: str = "anthropic"
    model: str = "claude-sonnet-4-20250514"
    temperature: float = 0.0
    max_tokens: int = 1024
    api_key: str = ""       # 从环境变量读取
    base_url: str = ""      # 自定义端点

@dataclass
class MemoryConfig:
    enabled: bool = False
    strategy: str = "local"
    size: int = 20
    ttl: int = 3600
    redis_url: str = ""
    db_path: str = ""

@dataclass
class ReActConfig:
    max_steps: int = 10
    tool_timeout: int = 30
    observation_enabled: bool = True
    verbose: bool = False

@dataclass
class TerminationConfig:
    signals: List[str] = None   # 自定义终止信号
    implicit_detection: bool = True
```

---

## 11. 完整执行流程图

```
用户输入
   │
   ▼
┌─────────────────────┐
│  Runtime.run(input)  │
└────────┬────────────┘
         │
    ┌────▼────┐
    │ Executor │
    │ .reduce()│
    └────┬────┘
         │
    ┌────▼──────────────────────────────────────────────────┐
    │                   Match Term Type                      │
    ├───────────────┬──────────┬──────────┬─────────────────┤
    │ConversationLam│  Compose  │  Loop   │ Tool/Route/...  │
    │  (preferred)  │           │(ReAct)  │                 │
    │  Lam (legacy) │           │         │                 │
    └───────┬───────┴─────┬────┴────┬────┴─────────────────┘
            │             │         │
    ┌───────▼───────┐┌───▼───┐ ┌──▼──────────────────────┐
    │ConversationLam││ f>>g  │ │    ReActEngine.run()     │
    │   .apply()    ││ 链式  │ │                           │
    │  ┌──────────┐ ││ 规约  │ │  ┌──────────────────────┐│
    │  │ manage   │ │└───┬───┘ │  │ Step Loop (Y 展开)   ││
    │  │ context  │ │    │     │  │                        ││
    │  └────┬─────┘ │    │     │  │ 1. THINK (Conv.Lam)   ││
    │  ┌────▼─────┐ │    │     │  │ 2. PARSE (Action)     ││
    │  │ Provider  │ │    │     │  │ 3. ROUTE (CASE)       ││
    │  │  .chat()  │ │    │     │  │ 4. INVOKE (Tool)      ││
    │  └────┬─────┘ │    │     │  │    ├─ MCP (HTTP)       ││
    │       │       │    │     │  │    ├─ CLI (subproc)    ││
    │  ┌────▼──────┐│    │     │  │    └─ terminate(λx.x)  ││
    │  │LLM Call   ││    │     │  │ 5. OBSERVE (format)    ││
    │  │ Anthropic ││    │     │  │ 6. UPDATE (Memory)     ││
    │  │ ClaudeCode││    │     │  │ 7. CHECK (terminate?)  ││
    │  │ Ollama/.. ││    │     │  │                        ││
    │  └────┬──────┘│    │     │  │  ── Loop or Return ──  ││
    │       │       │    │     │  └──────────────────────┘ │
    └───────┼───────┘    │     └──────────────┬────────────┘
         │                                │
    ┌────▼────────────────────────────────▼───────┐
    │              Context / Trace                  │
    │  ctx.log(term, input, output, duration, ...)  │
    └────┬────────────────────────────────────────┘
         │
    ┌────▼─────────┐
    │ Memory Update │
    │ auto_save()   │
    └────┬─────────┘
         │
    ┌────▼────┐
    │  Result  │
    └─────────┘
```

---

## 12. 与 from_config 的集成

### 12.1 编译 + 运行时的衔接

```python
# 目前: from_config 返回 Term，Term 自己执行自己
agent = from_config("config.yml")
result = agent("input")  # Term.__call__ → Term.apply

# 新方案: from_config 返回 Term，Runtime 执行 Term
agent = from_config("config.yml")
runtime = Runtime.from_config("config.yml")
result = runtime.run(agent, "input")

# 或者一步到位:
result = Runtime.execute("config.yml", "input")
```

### 12.2 Runtime.execute 一站式入口

```python
class Runtime:
    """
    lambdagent 运行时。

    Lambda 语义:
        Runtime = (Executor, Γ, trace)
        Runtime.execute(config, input) =
            let term = compile(config) in
            let ctx  = Context.new() in
            Executor.reduce(term, input, ctx)
    """

    @staticmethod
    def execute(
        config_path: str,
        input: str,
        **overrides,
    ) -> RuntimeResult:
        """
        一站式执行：编译 + 初始化运行时 + β-规约。

        等价于:
            from_config(config) → term
            Runtime(config) → runtime
            runtime.run(term, input) → result

        Returns:
            RuntimeResult(result, trace, stats)
        """
        # 1. 加载配置
        config = RuntimeConfig.from_yaml(config_path, **overrides)

        # 2. 编译 Lambda 项
        term = from_config(config_path)

        # 3. 初始化运行时
        runtime = Runtime(config)

        # 4. 执行
        return runtime.run(term, input)

@dataclass
class RuntimeResult:
    result: str              # 最终结果
    trace: List[TraceRecord] # β-规约追踪
    stats: TraceStats        # 统计信息
    context: Context         # 执行上下文（含 Memory 最终状态）
```

---

## 13. 实现优先级

### 13.1 分层实现计划

```
P0 — 能跑起来 (MVP)
├── ActionParser (JSON 解析 + 关键词兜底)         ~150 行
├── MCPClient (同步 HTTP，单连接)                  ~200 行
├── TerminationOracle (显式 + 隐式)               ~80 行
├── ReActEngine (7 阶段完整流程)                   ~300 行
├── LocalMemory (dict + TTL + LRU)                ~100 行
└── LLMAdapter (Anthropic only)                   ~80 行
                                            合计: ~910 行

P1 — 能用起来 (Production)
├── RedisMemory                                   ~150 行
├── 多 Provider (OpenAI, DashScope)               ~200 行
├── CLI Agent 集成到 ReActEngine                  ~100 行
├── TraceStore (JSON 导出 + 统计)                 ~150 行
├── 错误重试 + 降级                               ~100 行
└── Runtime.execute 一站式入口                    ~80 行
                                            合计: ~780 行

P2 — 好用起来 (Polish)
├── MCP SSE 流式                                  ~150 行
├── 异步执行 (asyncio)                            ~200 行
├── Tool Schema 自动发现                          ~100 行
├── Memory auto_save 策略 2 (LLM 摘要)            ~80 行
├── Trace 火焰图 / 时间线可视化                    ~150 行
└── REPL 增强 (热更新/断点/回放)                  ~200 行
                                            合计: ~880 行
```

### 13.2 模块依赖图

```
Runtime.execute
    │
    ├── from_config (已有)
    │
    └── Runtime.run
         │
         └── Executor.reduce
              │
              ├── ConversationLam      ← 已完成
              │   └── LLMProvider      ← 已完成
              │       ├── ClaudeCodeProvider   (session persistence)
              │       ├── AnthropicProvider     (Messages API)
              │       └── OpenAICompatProvider  (Ollama/OpenAI/...)
              │
              ├── ReActEngine          ← P0
              │   ├── ActionParser     ← P0
              │   ├── TerminationOracle← P0
              │   ├── MCPClient        ← P0
              │   │   └── MCPPool      ← P2
              │   ├── CLIAgent         ← P1 (已有 shell_tool.py)
              │   └── ObservationFmt   ← P0 (内联)
              │
              ├── MemoryBackend        ← P0 (Local), P1 (Redis)
              │
              └── TraceStore           ← P1
```

---

## 14. 文件结构

```
lambdagent/
├── __init__.py              # 已有
├── __main__.py              # 已有
├── core.py                  # 已有 (Term, Context, TraceEntry)
├── primitives.py            # 已有 (Lam, Compose, Loop, Tool, ...)
├── extensions.py            # 已有 (Memory, Route, Guard, Par)
├── conversation.py          # 新增: ConversationLam (对话感知 Lambda 抽象)
├── dataset.py               # 已有
├── fromconfig/
│   ├── compiler.py          # 已有 (YAML → Term 编译器, 含 _create_provider)
│   ├── errors.py            # 已有
│   └── schema.py            # 已有
├── providers/               # 新增: LLM Provider 层
│   ├── base.py              # LLMProvider ABC + ProviderConfig + ProviderError
│   ├── claude_code_provider.py  # ClaudeCodeProvider (session persistence)
│   ├── anthropic_provider.py    # AnthropicProvider (Messages API)
│   └── openai_compat_provider.py # OpenAICompatProvider (Ollama/OpenAI/...)
├── lint.py                  # 已有
│
├── runtime/                 # ← 新增: 运行时
│   ├── __init__.py
│   ├── executor.py          # Executor (β-规约引擎)
│   ├── react_engine.py      # ReActEngine (Y 组合子展开)
│   ├── action_parser.py     # ActionParser (LLM 输出解析)
│   ├── termination.py       # TerminationOracle (停止判定)
│   ├── llm_adapter.py       # LLMAdapter (多 Provider)
│   ├── mcp_client.py        # MCPClient (MCP 协议)
│   ├── memory_backends.py   # LocalMemory, RedisMemory
│   ├── trace_store.py       # TraceStore (追踪持久化)
│   ├── config.py            # RuntimeConfig (配置合并)
│   └── runtime.py           # Runtime (一站式入口)
│
├── cli/                     # 已有
│   ├── __init__.py
│   ├── main.py              # CLI 命令
│   └── shell_tool.py        # ShellTool + CLIAgent
```

---

## 15. 验收标准

### 15.1 MVP 验收 (P0)

```
测试: lambdagent run agent-cofig.yml "计算 1+2+3+4+5 的和"

期望行为:
  β[0] think     (2.5s)  → "我需要计算 1+2+3+4+5。让我调用 sum 工具。"
                            {"action": "everything_get_sum", "input": {"numbers": [1,2,3,4,5]}}
  β[1] parse     (0.0s)  → Action(tool="everything_get_sum", input={...})
  β[2] invoke    (1.0s)  → MCP POST https://ai-paas.../mcp/airouting
                            → "15"
  β[3] observe   (0.0s)  → "Observation: The sum is 15."
  β[4] think     (2.0s)  → "结果是 15。任务完成。"
                            {"action": "terminate", "answer": "1+2+3+4+5 = 15"}
  β[5] terminate (0.0s)  → (base case: λx.x)

  Result: 1+2+3+4+5 = 15
  (6 β-reductions, 5.5s, ~800 tokens)
```

### 15.2 各组件验收

| 组件 | 测试 | 通过标准 |
|---|---|---|
| ActionParser | 10 种 LLM 输出格式 | 8/10 正确解析 |
| MCPClient | 调用真实 MCP 端点 | 返回非 stub 结果 |
| TerminationOracle | 显式 + 隐式 + 强制 | 3/3 正确判定 |
| ReActEngine | 完整 ReAct 循环 | 在 maxSteps 内完成 |
| LocalMemory | 写入 + 读取 + TTL + LRU | 全部通过 |
| LLMAdapter | 调用 Anthropic API | 返回有效 response |
| Runtime.execute | 端到端执行 | 从 YAML 到结果，无手动步骤 |

### 15.3 端到端验收

```bash
# 测试 1: simple agent
lambdagent run simple.yml "Hello" --trace
# 期望: 1 步 β-规约，返回 LLM 输出

# 测试 2: react agent with terminate
lambdagent run react.yml "1+1=?" --trace
# 期望: 2-5 步，最终调用 terminate

# 测试 3: react agent with MCP tool
lambdagent run agent-cofig.yml "计算 1+2+...+100" --trace
# 期望: 调用 MCP 工具，返回 5050

# 测试 4: chain agent
lambdagent run chain.yml "长文本..." --trace
# 期望: N 步 β-规约，N = chain steps 数

# 测试 5: REPL with memory
lambdagent repl agent-cofig.yml
λ> 记住：我的名字是 Alice
λ> 我叫什么？
# 期望: "Alice"（Memory 跨轮次保持）

# 测试 6: CLI agent 互调
lambdagent run master.yml "研究量子计算" --tool sub="lambdagent run worker.yml --quiet -"
# 期望: master 调用 worker，worker 返回结果
```

---

## 16. Lambda 对应速查

```
运行时概念               Lambda 演算对应
──────────              ──────────────
Executor.reduce          β-规约  (λx.M)N → M[x:=N]
ReActEngine.run          Y_n(g)(x) 展开
_step (单步)             一次 β-规约
ActionParser.parse       模式匹配 (CASE)
terminate tool           λx.x (identity = base case)
TerminationOracle        base case 检测
LLMAdapter.call          (λ_D x) → F_{M,D}(x)
MCPClient.invoke         Tool(name, fn)(input)
CLIAgent.__call__        跨进程 β-规约
MemoryBackend.read       Γ.lookup(x)
MemoryBackend.write      Γ' = Γ ∪ {k: v}
Context.trace            β-规约链记录
RuntimeConfig            Γ₀ (初始环境)
Runtime.execute          eval(compile(source), Γ₀)
```

---

## 17. 多智能体模块 (multiagent.py)

> 5 个新构造，扩展 Lambda 演算至 π-演算（进程演算）级别。

### 17.1 Channel / Send / Receive — π-calculus 通道

```
Lambda + π 语义:
    Channel()      = 创建通道 ν(c)
    Send(agent, c) = λx. let v = agent(x) in c!(v); v
    Receive(c, h)  = λ_. let v = c?() in h(v)
```

`Channel` 是线程安全的消息队列 (`queue.Queue`)，支持：
- 有缓冲/无缓冲通道（`capacity` 参数）
- 阻塞/超时读写
- 历史记录（direction, msg, timestamp）
- `close()` 关闭通道

`Send` 和 `Receive` 是 Term 子类，包装 Channel 操作为可组合的 Lambda 项。

### 17.2 SharedMemory — 共享环境

```
Lambda 语义:
    SharedMemory(store) = Γ_shared
    sm.wrap(agent)      = λx. agent(x) [Γ ∪ Γ_shared]
    sm.read(key)        = Γ_shared(key)
    sm.write(key, v)    = Γ_shared[key ↦ v]
```

线程安全（`threading.RLock`），支持 `append_only` 模式（对应 Preservation 定理 Σ' ⊇ Σ 约束 — 已有 key 不可改变类型）。

### 17.3 GroupChat — 群组对话

```
Lambda 语义:
    GroupChat([a,b,c], scheduler, n) =
        Y_n(λself.λstate.
            let speaker = scheduler(state) in
            let msg = speaker(state) in
            IF done(state') THEN state' ELSE self(state')
        )
```

这是 Loop + Route 的组合，不引入新 Lambda 构造。调度策略：
- `"round_robin"`: 固定轮流
- `"random"`: 随机选择
- `Term`: LLM 分类器动态选择发言者

终止条件：包含 "CONSENSUS"/"DONE"/"TERMINATE" 等关键词，或自定义 `termination` 函数。

### 17.4 Handoff — 动态委派

```
Lambda 语义:
    Handoff(selector, registry) =
        λx. let target = selector(x) in
             registry[target](x)
```

与 Route 的区别：Route 是编译时静态路由，Handoff 是运行时动态路由。支持：
- `register(name, agent)` / `unregister(name)` — 运行时动态扩展路由表
- 精确匹配 + 模糊匹配
- `fallback` Agent

### 17.5 AsyncPar — 真并行

```
Lambda 语义:
    AsyncPar(f, g) = λx. let (r₁, r₂) = concurrent(f(x), g(x)) in (r₁, r₂)
```

使用 `ThreadPoolExecutor` 并发执行。与 `Par` 的区别：`Par` 是顺序执行，`AsyncPar` 是线程池真并发。支持 `|` 操作符展平。

### 17.6 并发实现细节

#### Par vs AsyncPar 对比

```
Par (假并行):
    def apply(self, input, ctx):
        return tuple(a.apply(input, ctx) for a in self.agents)
    # generator 顺序执行: a1 完成 → a2 完成 → a3 完成
    # 3 个 Agent 各 1s → 总共 3s

AsyncPar (真并行):
    def apply(self, input, ctx):
        with ThreadPoolExecutor(max_workers=len(self.agents)) as executor:
            futures = {executor.submit(_run, i, a): i for i, a in enumerate(self.agents)}
            for future in as_completed(futures):
                idx, result = future.result()
                results[idx] = result
        return tuple(results)
    # 线程池并发: a1, a2, a3 同时开始
    # 3 个 Agent 各 1s → 总共约 1s (3x 加速)
```

#### 为什么选择线程池而非 asyncio / multiprocessing

```
LLM API 调用特征:
  - I/O 密集（等 HTTP 响应 2-3 秒）
  - CPU 几乎不用（不做矩阵运算）
  - Python GIL 对 I/O 等待无影响

ThreadPoolExecutor:  ✅ 最优选择，简单且高效
asyncio:             ⚠️ 更高效但所有代码需 async/await，破坏 Term.apply() 接口
multiprocessing:     ❌ 过重，跨进程序列化 Context/Term 困难
```

#### Channel 线程安全实现

```python
class Channel:
    _queue: queue.Queue    # Python 标准库线程安全队列
    _closed: bool
    _lock: threading.Lock  # 保护 history 记录

    # capacity=0 → 无缓冲（同步模式）
    #   send() 阻塞直到 receive() 被调用
    #   对应 π-calculus 的同步通信: c!(v) 与 c?(x) 握手
    #
    # capacity>0 → 有缓冲（异步模式）
    #   send() 不阻塞（直到缓冲区满）
    #   对应 π-calculus 的异步通信: 有界邮箱
```

#### SharedMemory 线程安全实现

```python
class SharedMemory:
    _store: Dict[str, Any]
    _lock: threading.RLock           # 可重入锁（同一线程可多次获取）
    _type_registry: Dict[str, type]  # 首次写入的类型记录

    def write(self, key, value):
        with self._lock:  # 加锁
            # append_only 模式：类型检查（Σ'⊇Σ）
            if self._append_only and key in self._type_registry:
                expected = self._type_registry[key]
                if not isinstance(value, expected):
                    raise TypeError(...)  # 类型违反，对应 Preservation 定理
            self._store[key] = value
```

`RLock`（可重入锁）而非 `Lock`：因为 Agent 内部可能嵌套调用 `read()` 和 `write()`，普通 `Lock` 会死锁。

#### A2AServer 并发模型

```python
class A2AServer:
    # 基于 http.server.HTTPServer + threading
    # 每个入站 HTTP 请求在独立线程中处理
    # 每个请求 = 一次远程 β-规约

    def start(self, background=True):
        self._server = HTTPServer((host, port), Handler)
        if background:
            thread = Thread(target=self._server.serve_forever, daemon=True)
            thread.start()  # 后台运行，不阻塞主线程
```

#### 并发安全总结

| 组件 | 机制 | 粒度 | 安全保证 |
|---|---|---|---|
| AsyncPar | ThreadPoolExecutor | 每个 Agent 一个线程 | 结果按原始顺序返回 |
| Channel | queue.Queue | 消息级别 | FIFO，阻塞/超时 |
| SharedMemory | threading.RLock | 读写操作级别 | 互斥访问 + 类型安全 |
| A2AServer | HTTPServer + threading | 每个请求一个线程 | 无状态处理 |
| Context.trace | list.append | 追加级别 | Python list.append 是原子的 |

---

## 18. Skill 系统 (skills.py)

### 18.1 核心概念

```
Lambda 语义:
    Skill     = let name = λx.body in ...   (命名 Lambda 项 + 元数据)
    Registry  = Γ_skills : Name → Skill     (技能注册表 = 特殊环境)
    Discovery = Route(LLM, Γ_skills)        (LLM 从注册表选择技能)
```

### 18.2 Skill

`Skill` 继承 `Term`，额外提供：
- `description` — 自然语言描述（用于 LLM 发现）
- `signature` — 类型签名 `SkillSignature(input_type, output_type)`
- `tags` — 标签（用于搜索过滤）
- `examples` — 使用示例（用于 few-shot）
- `bind(**kwargs)` — 偏应用（柯里化）
- `>>` 组合时进行类型兼容性检查
- `stats` — 使用统计（调用次数、平均耗时）
- `to_dict()` — 序列化

### 18.3 SkillRegistry

单例模式的全局注册表。支持：
- `register(skill)` / `register_pack(pack)`
- `search(query, tags)` — 文本 + 标签搜索
- `discover(task, classifier)` — LLM 驱动的技能发现
- `build_route()` — 构建为 Route 可用的字典
- `stats()` — 注册表统计

### 18.4 SkillAgent

```
SkillAgent(classifier, registry) =
    λx. let skill = discover(classifier, registry, x) in skill(x)
```

Handoff 的技能化版本：自动从 Registry 发现并执行最佳 Skill。

### 18.5 @skill 装饰器

```python
@skill("summarize", "Summarize text", tags=["writing"])
def summarize(x):
    return f"Summary: {x[:50]}..."
```

自动包装为 Skill 并注册到全局 Registry。

---

## 19. MCP Client (mcp_client.py)

### 19.1 架构

```
MCPServer ──→ MCPTransport (HTTP / stdio)
    │
    ├── list_tools()    → tools/list
    ├── call_tool()     → tools/call
    ├── read_resource() → resources/read
    │
    ├── to_tool(name)   → MCPTool (lambdagent Term)
    ├── to_tools()      → [MCPTool, ...]
    └── to_route_dict() → {name: MCPTool, ...}
```

### 19.2 传输层

- `MCPHttpTransport` — Streamable HTTP (MCP 2025-11-25 spec), JSON-RPC 2.0 over POST
- `MCPStdioTransport` — 本地子进程 stdin/stdout 通信

### 19.3 MCPTool

```
Lambda 语义:
    MCPTool(server, name) = Tool(name, λx. mcp_call(server, name, parse(x)))
```

输入自动解析：str → JSON 尝试 → 单参数推断 → `{"input": x}` fallback。支持重试。

### 19.4 便利函数

```python
tools = mcp_tools("http://localhost:3000/mcp")     # 获取所有工具
search = mcp_tool("http://localhost:3000/mcp", "search")  # 获取单个工具
```

---

## 20. Checkpoint (checkpoint.py)

### 20.1 核心概念

```
Lambda 语义:
    Checkpoint = (Γ, trace, Γ_shared, last_input, n_steps)
    save(ctx)  = serialize(Γ, trace) → JSON 文件
    load(path) = deserialize(JSON) → Context
```

### 20.2 Checkpoint 类

包含：Context 全部状态 (bindings, trace, memory)、SharedMemory 状态、metadata、last_input、step_count。

序列化格式：JSON，带版本号 (`CHECKPOINT_VERSION = "1.0.0"`)，向后兼容检查。

### 20.3 CheckpointManager

管理多个 checkpoint：
- `save(ctx, description)` — 保存新 checkpoint（自动编号 cp_001.json, cp_002.json, ...）
- `list()` — 列出所有 checkpoint 摘要
- `latest()` — 获取最新
- `rollback()` — 回退到上一个
- `max_checkpoints` — 自动清理旧 checkpoint

### 20.4 序列化实现细节

**JSON 格式（checkpoint 文件结构）：**

```json
{
  "version": "1.0.0",
  "timestamp": 1711454400.0,
  "description": "after research step",
  "last_input": "分析 AI Agent 市场",
  "context": {
    "bindings": {"key": "value"},
    "memory": {"session_id": "demo_001"},
    "trace": [
      {
        "term_name": "research",
        "term_id": "abc12345",
        "input": "AI Agent 市场",
        "output": "研究结果: 3 个关键发现",
        "duration_ms": 2800.5,
        "model": "claude-sonnet",
        "tokens_used": 150
      }
    ]
  },
  "shared_memories": {
    "main": {"counter": 42, "trend": "上升"}
  },
  "stats": {
    "step_count": 5,
    "total_time_ms": 12400.0
  }
}
```

**序列化规则：**

```
_serialize_value(v):
  str/int/float/bool/None → 直接保留
  list/tuple              → 递归序列化每个元素
  dict                    → 递归序列化每个值，key 转 str
  其他 (Term, 函数等)     → str(v)  (不可序列化的降级为字符串)
```

**版本兼容策略：**

```
加载时检查 version:
  主版本号不同 → CheckpointVersionError（不兼容）
  次版本号不同 → 警告但继续（向后兼容）
  修订号不同   → 静默通过
```

**CheckpointManager 文件命名：**

```
./checkpoints/my_agent/
  ├── cp_001.json    ← 第 1 个 checkpoint
  ├── cp_002.json    ← 第 2 个 checkpoint
  └── cp_003.json    ← 第 3 个（最新）

max_checkpoints=5 时，超出的旧文件自动删除（FIFO）
rollback() = 删除最新 + 加载倒数第二个
```

### 20.5 便利函数

```python
save_context(ctx, "checkpoint.json", last_input="input")
ctx = load_context("checkpoint.json")
```

---

## 21. A2A Protocol (a2a.py)

### 21.1 架构

```
lambdagent Skill ──→ AgentCard (JSON) ──→ A2AServer (HTTP)
                                              │
                                        A2AClient (Term)
                                              │
                               远程 Agent 封装为本地 Term
```

### 21.2 AgentCard

A2A Agent 能力描述文档，对应 `/.well-known/agent.json`。包含 name, description, url, skills, input/output modes, authentication。扩展字段 `x-lambdagent` 保存 lambda_type 和 tags。

映射：`Skill -> AgentCard`，`SkillRegistry -> AgentCard`（整个注册表打包为一个 Card）。

### 21.3 A2AServer

```
Lambda 语义:
    A2AServer(agent) = HTTP 服务器
        GET  /.well-known/agent.json → AgentCard
        POST /                        → JSON-RPC (tasks/send, tasks/get)
```

Task 生命周期：submitted -> working -> completed | failed | canceled。

### 21.4 A2AClient

```
Lambda 语义:
    A2AClient(url) = Tool(agent_name, λx. a2a_send_task(url, x))
```

远程 Agent 封装为本地 Term，可参与 `>>` / `Route` / `Loop` 等组合。

---

## 22. RAG (rag.py)

### 22.1 核心概念

```
Lambda 语义:
    RAGTool(store, k)      = Tool("rag", λx. retrieve(store, x, k))
    AgenticRAG(agent, rag)  = λx. IF need_rag(x) THEN agent(x + rag(x)) ELSE agent(x)
```

RAG 不引入新 Lambda 构造——它是 Tool 的特化版本。

### 22.2 向量存储

两个后端，接口统一（`add` / `add_many` / `search`）：

#### SimpleVectorStore — 零依赖 TF-IDF 实现

**核心算法：TF-IDF 余弦相似度**

```
文档入库流程:
  "Lambda calculus was invented by Church"
       ↓ _tokenize()
  ["lambda", "calculus", "invented", "church"]  (小写 + 去停用词)
       ↓ _build_tfidf()
  {lambda: 0.42, calculus: 0.38, invented: 0.31, church: 0.35}  (稀疏向量)

查询流程:
  "What is lambda calculus?"
       ↓ _text_to_tfidf()
  {lambda: 0.71, calculus: 0.71}
       ↓ _cosine_similarity() 与每个文档向量
  doc_1: 0.89 ← 最相关
  doc_2: 0.12
  doc_3: 0.03
       ↓ sort + top_k
  返回 [doc_1]
```

**三步算法详解：**

```
Step 1: TF (词频) — 词在文档中的重要性
    TF(word, doc) = 该词出现次数 / 文档总词数
    "lambda calculus lambda" → TF("lambda") = 2/3 = 0.67

Step 2: IDF (逆文档频率) — 词在语料中的稀有度
    IDF(word) = log(总文档数 / 包含该词的文档数) + 1
    5 篇文档中 "lambda" 出现在 2 篇 → IDF = log(5/3) + 1 = 1.51
    "the" 出现在 5 篇 → IDF = log(5/6) + 1 = 0.82 (常见词权重低)

Step 3: 余弦相似度 — 两个向量的夹角
    cosine(A, B) = (A · B) / (|A| × |B|)
    = Σ(Aᵢ × Bᵢ) / (√Σ(Aᵢ²) × √Σ(Bᵢ²))
    范围: [0, 1]，1 = 完全相同，0 = 完全无关
```

**分词实现：**

```python
def _tokenize(self, text):
    text = text.lower()
    tokens = re.findall(r'[a-z]+|[\u4e00-\u9fff]', text)  # 英文词 + 中文字
    stops = {"the", "a", "an", "is", "are", "in", "on", ...}  # 停用词
    return [t for t in tokens if t not in stops and len(t) > 1]
```

**性能特征：**

| 指标 | SimpleVectorStore |
|---|---|
| 入库复杂度 | O(n × m)，n=文档数，m=平均词数 |
| 查询复杂度 | O(n × v)，n=文档数，v=词汇表大小 |
| 空间复杂度 | O(n × v) 稀疏矩阵（实际远小于此）|
| 适用规模 | < 1,000 文档 |
| 优势 | 零依赖，< 100 行核心代码 |
| 劣势 | 仅关键词匹配，不理解语义（"dog" ≠ "puppy"）|

**缓存机制：**

```python
self._tfidf_cache = None  # 首次查询时构建
# add() 后自动清除缓存
# 查询时若缓存存在则复用，避免重复计算 IDF
```

#### ChromaStore — ChromaDB 后端

```python
# 需要: pip install chromadb
class ChromaStore:
    def __init__(self, collection_name, persist_directory=None):
        import chromadb
        if persist_directory:
            self._client = chromadb.PersistentClient(path=persist_directory)
        else:
            self._client = chromadb.Client()  # 内存模式
        self._collection = self._client.get_or_create_collection(collection_name)
```

**与 SimpleVectorStore 对比：**

| 维度 | SimpleVectorStore | ChromaStore |
|---|---|---|
| 向量化 | TF-IDF（词频统计） | Embedding 模型（语义向量） |
| 维度 | 稀疏，维度=词汇表大小 | 稠密，384-1536 维 |
| 语义理解 | 仅关键词匹配 | 语义相似（"dog" ≈ "puppy"） |
| 依赖 | 零 | chromadb + embedding model |
| 持久化 | 无（内存） | SQLite + 文件系统 |
| 查询算法 | 暴力扫描 O(n) | ANN 近似最近邻 O(log n) |
| 适用规模 | < 1,000 文档 | 百万级 |

**切换后端只需改一行：**

```python
rag = create_rag(docs, backend="simple")   # 开发
rag = create_rag(docs, backend="chroma")   # 生产
```

### 22.3 RAGTool

输出格式：`[Source N, score=0.xxx] 文档内容`（numbered / plain / json）。

### 22.4 AgenticRAG

Agent 自行决定何时检索（而非每次都检索）。`decider` 参数可以是 Python 函数或 LLM Agent。

### 22.5 便利函数

```python
rag = create_rag(["doc1", "doc2", "doc3"], top_k=3, backend="simple")
```

---

## 23. 扩展后的 Lambda 对应速查

```
运行时概念                Lambda / π 演算对应
──────────               ──────────────────
Channel.send(v)           c!(v)       π-calculus 输出
Channel.receive()         c?(x).P     π-calculus 输入
SharedMemory.wrap(a)      a [Γ ∪ Γ_shared]  共享环境
GroupChat                 Y_n(Loop + Route)  群组对话
Handoff                   动态 CASE          运行时路由
AsyncPar                  concurrent(f(x), g(x))  并发 β-规约
Skill                     let name = term in ...   命名 Lambda 项
SkillRegistry             Γ_skills            技能环境
SkillAgent                discover >> execute  技能发现+执行
MCPTool                   Tool(name, mcp_call) MCP 工具封装
A2AClient                 Tool(name, a2a_call) 远程 Agent 封装
RAGTool                   Tool("rag", retrieve) 检索工具
AgenticRAG                IF need THEN rag+agent ELSE agent
Checkpoint.save           serialize(Γ, trace)  状态快照
Checkpoint.load           deserialize → Γ      状态恢复
SandboxedTool             λx.f(x) [f in sandbox]  隔离执行的 Tool
SecureExecutor            sandbox_all(Γ)       递归包裹所有 Tool
ResourceLimiter           RLIMIT constraints   POSIX 资源限制
```

---

## 24. Sandbox 运行时 (sandbox.py)

### 24.1 概述

Sandbox 模块提供 **进程级隔离** 的 Tool 执行环境。每个 `SandboxedTool` 在独立子进程中运行，通过 pickle IPC 通信，受 POSIX resource limits 约束。

Lambda 语义不变：`SandboxedTool` 仍是 `Tool` 的子类型，其指称语义与普通 Tool 完全相同。

```
⟦SandboxedTool(n, f, P)⟧ = λx.f(x)    [f 在 sandbox 中执行]
```

### 24.2 架构

```
调用方 (主进程)                     子进程 (sandbox)
─────────────                     ─────────────────
SandboxedTool.__call__(x)
    │
    ├── pickle.dumps(x) ──────→  stdin (子进程)
    │                              │
    │                              ├── ResourceLimiter.apply()
    │                              │     ├── RLIMIT_CPU   (timeout)
    │                              │     ├── RLIMIT_AS    (memory_mb)
    │                              │     ├── RLIMIT_NOFILE (max_fds)
    │                              │     └── RLIMIT_NPROC  (禁止 fork)
    │                              │
    │                              ├── f(x) 执行
    │                              │
    │                              └── pickle.dumps(result) ──→ stdout
    │
    ├── pickle.loads(result) ←────
    └── return result
```

### 24.3 SandboxPolicy 预设

三种安全策略预设，覆盖从严格到宽松的场景：

| 属性 | `strict()` | `default()` | `permissive()` |
|------|-----------|-------------|-----------------|
| `timeout` | 5s | 30s | 300s |
| `memory_mb` | 64 | 256 | 2048 |
| `network` | False | False | True |
| `allow_subprocess` | False | False | True |
| `max_output_bytes` | 4096 | 65536 | 1048576 |
| `max_fds` | 8 | 64 | 256 |

使用方式：

```python
from lambdagent import SandboxPolicy, SandboxedTool

# 预设策略
strict = SandboxPolicy.strict()
default = SandboxPolicy.default()
permissive = SandboxPolicy.permissive()

# 自定义
custom = SandboxPolicy(timeout=10, memory_mb=128, network=False)
```

### 24.4 SandboxedTool

将任意函数包裹为隔离执行的 Tool：

```python
from lambdagent import SandboxedTool, SandboxPolicy

tool = SandboxedTool("risky_calc", lambda x: eval(x), policy=SandboxPolicy.strict())
result = tool("2 + 3")  # 在隔离子进程中执行
```

`@sandboxed` 装饰器提供一行创建方式：

```python
from lambdagent import sandboxed

@sandboxed(timeout=10, memory_mb=128)
def my_tool(x):
    return expensive_computation(x)
```

### 24.5 SecureExecutor

递归遍历 Term 树，将所有 `Tool` 节点替换为 `SandboxedTool`：

```python
from lambdagent import SecureExecutor, SandboxPolicy

executor = SecureExecutor(policy=SandboxPolicy.default())
secure_term = executor.sandbox_all_tools(term_tree)
# term_tree 中所有 Tool 节点已被包裹为 SandboxedTool
```

### 24.6 ResourceLimiter

在子进程启动时，通过 POSIX `resource` 模块施加硬性限制：

| RLIMIT 常量 | 对应 SandboxPolicy 属性 | 作用 |
|-------------|------------------------|------|
| `RLIMIT_CPU` | `timeout` | CPU 时间上限（秒） |
| `RLIMIT_AS` | `memory_mb` | 地址空间上限 |
| `RLIMIT_NOFILE` | `max_fds` | 文件描述符上限 |
| `RLIMIT_NPROC` | `allow_subprocess=False` → 0 | 禁止创建子进程 |

### 24.7 异常体系

```
SandboxViolation (base)
├── TimeoutViolation      # RLIMIT_CPU 或 subprocess timeout 触发
├── MemoryViolation       # RLIMIT_AS 超限
└── OutputViolation       # 输出字节数超过 max_output_bytes
```

### 24.8 升级路径 (L1 → L2 → L3)

| 级别 | 方式 | 适用场景 |
|------|------|---------|
| **L1** | `@sandboxed` 装饰器 | 单个 Tool 快速隔离 |
| **L2** | `SandboxedTool(name, fn, policy)` | 精细控制单个 Tool 的策略 |
| **L3** | `SecureExecutor.sandbox_all_tools(tree)` | 整棵 Term 树一键安全化 |

### 24.9 Lambda 对应

```
运行时概念                Lambda 对应
──────────               ──────────
SandboxedTool(n,f,P)     λx.f(x) [f in sandbox]  — Tool 类型不变
SecureExecutor           map(sandbox_wrap, Tools(Γ)) — 遍历环境
SandboxPolicy            执行策略参数 — 不影响 λ 语义
ResourceLimiter          操作语义层面的资源约束
SandboxViolation         β-规约失败（异常终止）
```
