Agent 面试指南

Agent Context Management 设计范式与工程实践

Context Manager 不是聊天历史管理器,而是把事件事实、当前状态、长期记忆、外部证据、工具环境和系统策略编译成下一次行动所需最小充分上下文的注意力治理层。

Agent Context Management 设计范式与工程实践

这篇把前两版合并成一套完整文章:先讲长期稳定的设计范式和原则,再落到模块设计、字段建模、压缩策略、推理链路、工程架构,最后对比 Codex、Claude Code、Pi、Pydantic AI、LangGraph、AutoGen、LlamaIndex 等项目的落地方式。

核心观点先放前面:

Agent Context Manager 不是聊天历史管理器,而是一个把事件事实、当前状态、长期记忆、外部证据、工具环境和系统策略,编译成下一次行动所需最小充分上下文的注意力治理层。

换句话说,它不是简单地管理 messages[],也不是在上下文满了之后“总结一下”。它更像 agent 的 Attention Operating System:负责决定 agent 此刻应该知道什么、为什么知道、来源是什么、是否可信、是否过期、是否应该暴露给模型、是否应该写入长期记忆,以及如何在有限预算下保持推理连续性。


1. 从第一性原理定义 Context

很多 agent 系统一开始会把 context manager 理解成:

system prompt + historical messages + tool results + summary
    

这个理解能跑 demo,但支撑不了长期任务、复杂工具链、多分支、多 agent、长期记忆和审计需求。

更稳定的定义应该是:

Context_t = f(
      Intent,
      State,
      Evidence,
      Memory,
      Policy,
      Tools,
      Environment,
      Recent Interaction,
      Budget
    )
    

也就是:

某一时刻的上下文,是 agent 为了完成下一步判断、行动和表达所需的信息环境。

这带来一个重要转变:

不要问:历史消息怎么塞进模型?
    而要问:下一步行动需要哪些信息?
    

Context Manager 要持续回答这些问题:

agent 当前目标是什么?
    当前任务完成到哪一步?
    哪些事实已经被验证?
    哪些只是猜测?
    哪些工具结果是关键证据?
    哪些用户约束不可丢?
    哪些旧信息已经不相关?
    哪些内容虽然旧,但未来仍必须保留?
    哪些信息可以压缩?
    哪些信息必须原样保留?
    哪些信息可以进入模型?
    哪些信息只能留在系统内部?
    

这个抽象比较耐久。即使未来模型上下文窗口越来越大,agent 仍然需要处理注意力噪音、证据溯源、权限隔离、记忆污染、状态恢复、分支探索、成本预算和可解释性问题。Codex 的官方 subagent 文档也明确提到,即使有大上下文窗口,主对话被探索笔记、测试日志、堆栈、命令输出等中间噪声淹没后,可靠性仍会下降;它把这类问题称为 context pollution 和 context rot,并建议让 subagent 返回摘要而不是原始中间输出。(OpenAI开发者)


2. 核心范式:Context 是编译视图,不是事实源

这是整套设计最重要的原则。

2.1 错误范式

messages 就是上下文
    summary 替换历史
    模型看到什么,系统就只保存什么
    

这种设计早期快,但后期会遇到一堆问题:

无法恢复
    无法审计
    无法回放
    无法分支
    无法解释
    无法知道 summary 丢了什么
    无法区分事实、推测和用户偏好
    无法做权限与隐私治理
    

2.2 正确范式

Raw Events / Raw Messages / Tool Results / Artifacts 是事实源
    State / Summary / Memory / Trace 是语义投影
    ContextBundle / Prompt 是一次性编译产物
    

可以画成:

事实源层
      Event Log
      Raw Messages
      Tool Results
      Artifacts
      File Diffs
      External Observations
    
            ↓ projection / retrieval / compression
    
    语义层
      State
      Summary
      Memory
      Decision Log
      Evidence Graph
      Trace
    
            ↓ context compilation
    
    模型输入层
      Context Bundle
      Prompt Payload
      Tool Schemas
      Model-specific Messages
    

所以,发给模型的上下文只是一次临时视图。它可以被压缩、裁剪、重排、脱敏、适配不同模型协议,但不能成为系统唯一事实来源。

一句话:

Context 是编译产物,不是数据库。


3. 稳定的 Context Ontology

一个成熟 agent 的上下文系统,至少应该区分这些对象:

Session      工作容器
    Event        发生过的事实
    Message      用户、模型、工具之间的通信记录
    State        当前任务状态投影
    Stats        token、成本、延迟、压缩率等指标
    Memory       可跨时间复用的知识
    Artifact     大对象、文件、diff、日志、截图、工具输出
    Trace        可解释的决策与行动链路
    Policy       权限、安全、隐私、预算、工具边界
    ContextBundle 一次模型调用的上下文编译结果
    

它们的职责不同,不能混在一个 messages[] 里。


3.1 Session:工作容器

Session 回答的是:

这是谁的任务?
    在哪个 workspace / project / repo 下?
    用哪个 agent 和模型配置?
    当前权限是什么?
    是否可以恢复?
    是否有分支?
    当前 leaf 在哪里?
    

推荐抽象:

type AgentSession = {
      session_id: string;
      root_session_id?: string;
      parent_session_id?: string;
      branch_id?: string;
      leaf_event_id?: string;
    
      user_id?: string;
      org_id?: string;
      workspace_id?: string;
      project_id?: string;
    
      agent_id: string;
      agent_version: string;
    
      status: "active" | "paused" | "completed" | "failed" | "archived";
    
      cwd?: string;
      repo_root?: string;
    
      model_config: {
        provider: string;
        model: string;
        reasoning_level?: "low" | "medium" | "high";
        temperature?: number;
        max_output_tokens?: number;
      };
    
      context_config: {
        context_window_tokens: number;
        reserved_output_tokens: number;
        max_recent_tokens: number;
        max_retrieval_tokens: number;
        compression_policy_id: string;
      };
    
      permission_config: {
        sandbox_mode?: "read_only" | "workspace_write" | "danger_full_access";
        approval_policy?: "never" | "on_request" | "on_failure" | "always";
        allowed_tools?: string[];
        blocked_tools?: string[];
      };
    
      created_at: string;
      updated_at: string;
      last_active_at: string;
    };
    

Codex CLI 的 resume 就体现了 session 作为工作容器的价值:官方文档说 Codex 会本地保存 transcripts,用户可以用 codex resume 恢复之前的线程;恢复后会保留原始 transcript、plan history 和 approvals。(OpenAI开发者) Claude Code 也把会话 transcript 存成 JSONL,每行是 message、tool use 或 metadata entry。(Claude Code) Pi 的 session 更进一步,直接把 session 存成 JSONL 树结构,通过 id / parentId 支持原地分支。(Pi)


3.2 Event:事实源

Event 回答的是:

发生了什么?
    谁触发的?
    什么时候发生?
    因为什么发生?
    产生了什么结果?
    

它应该是 append-only 的黑匣子记录仪。

推荐抽象:

type AgentEvent = {
      event_id: string;
      session_id: string;
      run_id?: string;
      parent_event_id?: string;
    
      seq: number;
      timestamp: string;
    
      type:
        | "SessionStarted"
        | "UserPromptSubmitted"
        | "ContextBuilt"
        | "ModelRequestStarted"
        | "ModelResponseReceived"
        | "AssistantMessageCreated"
        | "ToolCallRequested"
        | "ToolCallStarted"
        | "ToolCallFinished"
        | "ToolCallFailed"
        | "ApprovalRequested"
        | "ApprovalGranted"
        | "ApprovalDenied"
        | "FileRead"
        | "FileWritten"
        | "DiffApplied"
        | "StateUpdated"
        | "MemoryRetrieved"
        | "MemoryWritten"
        | "CompactionStarted"
        | "CompactionCompleted"
        | "BranchCreated"
        | "SubagentStarted"
        | "SubagentFinished"
        | "VerifierStarted"
        | "VerifierFinished"
        | "ErrorRaised";
    
      actor: "user" | "agent" | "model" | "tool" | "system" | "subagent";
    
      payload: Record<string, unknown>;
    
      causality: {
        caused_by_event_id?: string;
        message_id?: string;
        tool_call_id?: string;
        artifact_id?: string;
        compaction_id?: string;
      };
    
      visibility: "internal" | "user_visible" | "audit" | "debug";
    };
    

为什么 Event 很重要?

因为很多关键事情不是 message:

这次 context build 选择了哪些 memory?
    模型调用用了哪个模型?
    工具调用是否被用户批准?
    某个文件什么时候被修改?
    哪次 compaction 后状态发生变化?
    subagent 返回了什么摘要?
    verifier 为什么失败?
    

Codex 的 hooks 设计正好体现了事件驱动 runtime 的思路:它把 hooks 组织在 SessionStartPreToolUsePostToolUsePreCompactSubagentStartStop 等事件上,并且要求非托管 hooks 经过 trust review。(OpenAI开发者) Claude Code 也把 hooks 定义为在生命周期特定点执行的 shell commands,用来提供 deterministic control,而不是依赖 LLM 自己选择是否执行规则。(Claude Code)


3.3 Message:通信协议,不是完整世界模型

Message 回答的是:

用户、模型、工具之间交换了什么?
    哪些内容进入过模型上下文?
    tool call 和 tool result 是什么?
    

但 message 不应该承担所有状态职责。

推荐抽象:

type Message = {
      message_id: string;
      session_id: string;
      run_id?: string;
      parent_message_id?: string;
      branch_id?: string;
    
      role:
        | "system"
        | "developer"
        | "user"
        | "assistant"
        | "tool"
        | "memory"
        | "summary"
        | "custom";
    
      content: MessageBlock[];
    
      tool_call?: {
        tool_call_id: string;
        tool_name: string;
        args: unknown;
        status: "pending" | "running" | "success" | "error";
      };
    
      tool_result?: {
        tool_call_id: string;
        output_ref?: string;
        output_preview?: string;
        error?: string;
        truncated: boolean;
      };
    
      usage?: {
        input_tokens?: number;
        output_tokens?: number;
        cached_tokens?: number;
        cost_usd?: number;
      };
    
      context_policy: {
        include_in_context: boolean;
        priority: number;
        max_tokens?: number;
        expires_at?: string;
        redaction_policy?: string;
      };
    
      provenance: {
        source: "user" | "model" | "tool" | "memory" | "summary" | "system";
        source_refs?: string[];
        hash?: string;
      };
    
      created_at: string;
    };
    

关键不变量:

tool call 和 tool result 必须成对保留
    不能从中间截断 pending tool call
    大型 tool output 应进入 artifact store,message 只保留 preview + ref
    summary message 必须知道它总结了哪些 messages/events
    custom/internal message 要明确是否进入 LLM context
    

Pydantic AI 的 typed message history 设计值得参考:它支持把已有 messages 传给下一次 run 以维持上下文,也给 ModelRequest / ModelResponse 打上 run_idconversation_id,从而把单次 run 和多轮 conversation 区分开。(Pydantic) 它还提供 ProcessHistory,允许在每次 model request 前修改 message history,例如出于隐私、token 成本或自定义处理逻辑。(Pydantic)


3.4 State:当前工作态投影

State 回答的是:

agent 当前认为任务到哪了?
    目标是什么?
    计划是什么?
    哪些事实已确认?
    哪些问题未解决?
    哪些文件读过、改过?
    下一步是什么?
    

推荐抽象:

type AgentState = {
      session_id: string;
      version: number;
      source_event_id: string;
      updated_at: string;
    
      goal: {
        user_goal: string;
        current_task?: string;
        acceptance_criteria?: string[];
      };
    
      plan: {
        steps: PlanStep[];
        current_step_id?: string;
        status: "planning" | "executing" | "blocked" | "verifying" | "done";
      };
    
      constraints: {
        hard_constraints: string[];
        soft_constraints: string[];
        user_preferences: string[];
        safety_constraints: string[];
      };
    
      facts: Array<{
        fact_id: string;
        content: string;
        confidence: number;
        source_refs: string[];
        expires_at?: string;
      }>;
    
      decisions: Array<{
        decision_id: string;
        decision: string;
        rationale_summary: string;
        alternatives_considered?: string[];
        source_refs: string[];
        created_at: string;
      }>;
    
      open_questions: Array<{
        question_id: string;
        question: string;
        blocking: boolean;
        owner?: "user" | "agent" | "tool" | "external";
      }>;
    
      artifacts: Array<{
        artifact_id: string;
        kind: "file" | "diff" | "command_output" | "url" | "image" | "dataset";
        path_or_uri: string;
        summary?: string;
        last_touched_at?: string;
        include_in_context?: boolean;
      }>;
    
      tool_state: {
        pending_tool_calls: string[];
        last_tool_results: string[];
        failed_tools: Array<{
          tool_call_id: string;
          error_summary: string;
          retryable: boolean;
        }>;
      };
    
      last_compaction_id?: string;
    };
    

State 的本质是投影,不是聊天记录。它应该可以从 events、messages、artifacts、memory 重建。LangGraph 的设计也很接近这个思路:它把短期记忆作为 agent state 的一部分,并通过 checkpointer 持久化,使 thread 可以恢复;其 persistence 文档还强调 checkpoint 支持 human-in-the-loop、memory、time travel、fault tolerance 和 fork alternative trajectories。(LangChain 文档)


3.5 Stats:观测指标,不要和 State 混在一起

Stats 管的是运行指标:

type AgentStats = {
      session_id: string;
      run_id?: string;
    
      token_usage: {
        input_tokens: number;
        output_tokens: number;
        cached_input_tokens?: number;
        reasoning_tokens?: number;
        context_window_tokens: number;
        context_used_tokens: number;
        reserved_output_tokens: number;
      };
    
      latency_ms: {
        context_build?: number;
        retrieval?: number;
        model_call?: number;
        tool_execution?: number;
        end_to_end?: number;
      };
    
      compression: {
        compaction_count: number;
        tokens_before_last_compaction?: number;
        tokens_after_last_compaction?: number;
        compression_ratio?: number;
      };
    
      tool_metrics: {
        tool_call_count: number;
        failed_tool_call_count: number;
        approval_requested_count: number;
        approval_denied_count: number;
      };
    
      quality_metrics?: {
        verifier_passed?: boolean;
        context_missing_risk?: "low" | "medium" | "high";
        hallucination_risk?: "low" | "medium" | "high";
      };
    };
    

这部分是后续 debug 和 eval 的基础。没有 stats,你很难回答:

为什么某类任务总是在 compaction 后失败?
    哪个 tool output 最占 token?
    哪些 memory 经常被召回但没用?
    context builder 的不同策略哪个更省钱?
    

3.6 Memory:治理后的知识,不是缓存

Memory 回答的是:

哪些知识值得未来复用?
    属于用户、项目、workspace,还是当前 session?
    是否可靠?
    是否敏感?
    是否过期?
    是否需要确认?
    

推荐抽象:

type Memory = {
      memory_id: string;
      scope: "user" | "project" | "workspace" | "session" | "global";
      kind: "preference" | "fact" | "instruction" | "skill" | "artifact_summary" | "decision";
    
      content: string;
      source_refs: string[];
      confidence: number;
    
      created_at: string;
      updated_at: string;
      expires_at?: string;
    
      access_policy: {
        sensitive: boolean;
        readable_by_agents: string[];
        redaction_policy?: string;
      };
    
      retrieval: {
        embedding_id?: string;
        keywords?: string[];
        priority: number;
      };
    };
    

Memory 设计最怕污染。不要把所有 summary 和用户每句话都塞进向量库。更稳的原则是:

长期记忆必须有 scope、source、confidence、ttl、sensitivity、priority 和 usage 记录。
    

Claude Code 把长期上下文拆成两类:CLAUDE.md 是用户写的持久指令,auto memory 是 Claude 根据纠正和偏好写下的笔记;官方文档也明确说这些会被当成 context,而不是强制配置,真正要阻止动作应使用 PreToolUse hook。(Claude Code) LangGraph 也明确区分 thread-scoped short-term memory 和跨 session 的 long-term memory,后者可以按自定义 namespace 保存和召回。(LangChain 文档) AutoGen 提供 Memory protocol,包含 queryupdate_contextaddclearclose 等方法,本质上也是“检索相关信息并注入 agent context”。(Microsoft GitHub)


3.7 Artifact:大对象和证据实体

Artifact 保存完整证据,而不是把所有东西塞进 prompt。

type Artifact = {
      artifact_id: string;
      session_id: string;
    
      kind:
        | "file"
        | "diff"
        | "tool_output"
        | "command_log"
        | "screenshot"
        | "dataset"
        | "url_snapshot";
    
      uri: string;
      summary?: string;
      content_hash?: string;
    
      source_event_id: string;
      created_at: string;
    
      access_policy?: {
        sensitive: boolean;
        allowed_scopes: string[];
      };
    };
    

正确模式:

context 中放摘要、引用、关键片段
    artifact store 中放完整证据
    

这对 coding agent 尤其重要。文件全文、测试日志、命令输出、截图、diff 都可能很大,不应该直接进入 message history。


3.8 Trace:可解释链路,不是隐藏思维链

Trace 回答的是:

agent 为什么这么做?
    依据是什么?
    验证过什么?
    下一步是什么?
    

不要把“保持推理链路”理解为保存模型隐藏 chain-of-thought。工程上更稳的做法是保存一条可公开、可审计、可回放的 Accountable Reasoning Trace

type ReasoningTrace = {
      trace_id: string;
      session_id: string;
      run_id: string;
    
      user_goal: string;
    
      assumptions: Array<{
        assumption: string;
        source_refs?: string[];
        confidence: number;
      }>;
    
      decision_log: Array<{
        decision_id: string;
        decision: string;
        rationale_summary: string;
        alternatives?: string[];
        evidence_refs: string[];
        created_at: string;
      }>;
    
      evidence_log: Array<{
        evidence_id: string;
        kind: "tool_result" | "file" | "user_message" | "memory" | "test" | "web" | "observation";
        ref: string;
        summary: string;
      }>;
    
      action_log: Array<{
        action_id: string;
        action_type: "message" | "tool_call" | "file_edit" | "memory_write" | "branch" | "compact";
        event_id: string;
        result_event_id?: string;
      }>;
    
      validation_log: Array<{
        check: string;
        result: "pass" | "fail" | "skipped";
        evidence_refs?: string[];
      }>;
    
      next_actions: string[];
    };
    

这条链路保存的是:

Goal
    Assumptions
    Evidence
    Decisions
    Actions
    Observations
    Validation
    Next Steps
    

这比保存自然语言 CoT 更稳、更安全,也更适合审计和恢复。


4. 十二条长期稳定的设计原则

这部分可以直接放进你的设计文档。

原则 1:上下文不是历史,而是行动所需的信息环境

不要追求完整复述历史,追求支持下一步高质量行动。


原则 2:事实源和派生物必须分离

Raw events / messages / artifacts = facts
    State / summary / memory / trace = derived projections
    ContextBundle = temporary compiled view
    

Summary 不能覆盖 raw history。


原则 3:所有有损变换必须可审计

压缩、截断、memory extraction、artifact summarization 都必须记录:

覆盖范围
    来源引用
    策略版本
    压缩前后 token
    质量检查结果
    first kept pointer
    

原则 4:State 是投影,不是聊天记录

agent 当前状态应该能从事件、消息、artifact 重建。


原则 5:Context 是编译结果,不是数据库

每次模型调用前,根据当前目标、预算、权限、相关性动态编译 context。


原则 6:Memory 是治理后的知识,不是缓存

Memory 要有作用域、来源、置信度、生命周期、敏感性和删除机制。


原则 7:工具调用是因果事件,不是普通文本

工具可能读取文件、修改文件、执行命令、访问网络、调用 API、产生费用、泄露隐私。它必须进入 event 和 trace。


原则 8:推理链路保存可解释骨架,而不是隐藏思维链

保存目标、假设、证据、决策、行动、验证,不依赖模型私有推理文本。


原则 9:上下文必须有作用域

至少区分:

global
    user
    organization
    workspace
    project
    session
    task
    step
    tool
    

当前任务假设不应该污染长期用户记忆;某项目的构建命令不应该污染另一个项目。


原则 10:上下文管理是预算分配

预算不只是 token:

token budget
    latency budget
    cost budget
    attention budget
    privacy budget
    risk budget
    tool budget
    

原则 11:分支和回滚是原生需求

agent 会探索、失败、回退、fork、merge。Session 不应该只是不可分叉的单链表。


原则 12:内部模型要稳定,外部协议可替换

不要把核心数据结构绑定到某个模型厂商的 message 格式、tool calling 格式、tokenizer 或 prompt 模板。

推荐:

Canonical Internal Model
      ↓ adapter
    Provider-specific Payload
    

5. Context Manager 的五个平面

可以把系统抽象为五个平面:

┌────────────────────────────────────────────┐
    │                Policy Plane                 │
    │  permission / privacy / safety / budget     │
    └────────────────────────────────────────────┘
    
    ┌────────────────────────────────────────────┐
    │              Semantic Plane                 │
    │  state / memory / summary / trace           │
    └────────────────────────────────────────────┘
    
    ┌────────────────────────────────────────────┐
    │               Evidence Plane                │
    │  events / messages / artifacts / tools      │
    └────────────────────────────────────────────┘
    
    ┌────────────────────────────────────────────┐
    │              Compilation Plane              │
    │  retrieve / rank / compress / redact / fit  │
    └────────────────────────────────────────────┘
    
    ┌────────────────────────────────────────────┐
    │              Execution Plane                │
    │  model calls / tool calls / validators      │
    └────────────────────────────────────────────┘
    

工程模块可以这样拆:

ContextManager
    ├── SessionStore
    ├── EventStore
    ├── MessageStore
    ├── StateProjector
    ├── MemoryStore
    ├── ArtifactStore
    ├── ContextBuilder
    ├── Compressor
    ├── TraceManager
    ├── PolicyManager
    └── Verifier / Eval
    

6. Context Builder:把上下文当成编译器产物

不要这样写:

prompt = system + history + summary + memory;
    

应该这样理解:

Context Builder = Context Compiler
    

它的输入是:

instructions
    current user request
    state
    events
    messages
    memories
    artifacts
    policies
    tools
    stats
    

它的编译 passes 是:

normalize
    retrieve
    rank
    deduplicate
    compress
    redact
    order
    fit budget
    validate
    emit trace
    

输出是:

model-specific context payload
    

推荐结构:

type ContextBundle = {
      system: PromptSegment[];
      developer_instructions: PromptSegment[];
      project_instructions: PromptSegment[];
    
      current_request: {
        user_message_id: string;
        text: string;
        parsed_intent?: string;
        acceptance_criteria?: string[];
      };
    
      session_state: AgentState;
    
      summaries: SummaryBlock[];
    
      recent_messages: Message[];
    
      retrieved_memories: RetrievedMemory[];
    
      retrieved_artifacts: RetrievedArtifact[];
    
      tool_context: {
        available_tools: ToolSpec[];
        pending_tool_pairs: Message[];
        recent_tool_results: Message[];
      };
    
      trace_context: {
        run_id: string;
        recent_decisions: string[];
        evidence_refs: string[];
      };
    
      budget: {
        max_context_tokens: number;
        reserved_output_tokens: number;
        used_tokens: number;
      };
    };
    

6.1 Context 选择优先级

无论未来模型怎么变,这个优先级大概率都稳定:

优先级 内容 是否可丢
P0 system / developer / safety / policy 不可丢
P0 当前用户请求 不可丢
P0 当前目标和验收标准 不可丢
P0 未完成 tool call / tool result pair 不可丢
P1 当前 state、plan、open questions 基本不可丢
P1 最近完整交互 tail 可裁剪,但不能破坏 turn
P1 当前工作文件、diff、测试结果、错误信息 可摘要
P2 历史决策、关键事实、项目规则 可摘要
P2 相关 memory / artifact 可裁剪
P3 旧闲聊、重复解释、冗长工具输出 优先丢弃或摘要

6.2 Context Builder 伪代码

async function buildContext(sessionId: string, userMessageId: string): Promise<ContextBundle> {
      const session = await sessionStore.get(sessionId);
      const state = await stateProjector.project(sessionId);
    
      const instructions = await loadInstructions(session);
      const latestSummary = await compressor.getLatestSummary(sessionId);
    
      const recentMessages = await messageStore.selectRecentCoherentTurns({
        sessionId,
        maxTokens: session.context_config.max_recent_tokens,
        preserveToolPairs: true,
      });
    
      const memories = await memoryStore.retrieve({
        query: state.goal.current_task ?? "",
        session,
        state,
        maxTokens: session.context_config.max_retrieval_tokens,
      });
    
      const artifacts = await artifactStore.retrieveRelevant({
        sessionId,
        state,
        maxTokens: 10_000,
      });
    
      const bundle = assembleByPriority({
        session,
        instructions,
        state,
        latestSummary,
        recentMessages,
        memories,
        artifacts,
        currentUserMessage: await messageStore.get(userMessageId),
      });
    
      return fitToBudget(bundle, {
        preserve: [
          "system",
          "current_request",
          "state.goal",
          "pending_tool_pairs",
        ],
        evictionOrder: [
          "verbose_tool_outputs",
          "low_relevance_artifacts",
          "old_assistant_chatter",
          "old_user_messages",
          "retrieved_memories",
        ],
      });
    }
    

7. 上下文压缩:语义蒸馏,不是文本缩短

压缩不是:

请总结上面对话。
    

压缩应该是:

从旧上下文中提取未来行动仍然需要的信息,并保留其来源关系。
    

一次好的 compaction 应该产出三类东西:

1. Summary
       给模型看的压缩上下文
    
    2. State Patch
       更新 agent 当前状态
    
    3. Memory Candidate
       候选长期记忆,经过审核后才写入 memory
    

不要把这三者混成一坨自然语言 summary。


7.1 何时触发压缩?

推荐触发器:

type CompressionTrigger =
      | "manual"
      | "soft_limit"
      | "hard_limit"
      | "branch"
      | "handoff"
      | "session_end";
    

配置示例:

const compressionPolicy = {
      softThresholdRatio: 0.75,
      hardThresholdRatio: 0.9,
      keepRecentTokens: 20_000,
      reserveOutputTokens: 16_000,
      preserveToolPairs: true,
      preserveCurrentTask: true,
    };
    

Pi 的实现是很好的工程参考:它有 compaction 和 branch summarization 两种机制;auto-compaction 触发条件是 contextTokens > contextWindow - reserveTokens,默认 reserveTokens 为 16384,默认保留最近 20k tokens,并支持手动 /compact。(GitHub)


7.2 压缩切点规则

推荐规则:

只在完整 turn 边界切
    不要把 assistant tool call 和 tool result 分开
    不要切掉 pending tool call
    大型 tool result 进入 artifact store,只在 summary 里保留关键事实与 ref
    保留最近 tail 原文,维持短期语境
    如果单 turn 巨大,先做 turn-level summary,再做 session-level summary
    

Pi 的 compaction 文档明确说明:通常在 turn 边界切;合法切点包括 user messages、assistant messages、bash execution 和 custom messages;永远不要在 tool result 处切,因为它必须和 tool call 保持在一起。(GitHub)


7.3 Summary 标准格式

不要让 summary 成为自由散文。建议结构化:

summary_id: cmp_123
    session_id: sess_456
    
    summarized_range:
      from_message_id: msg_001
      to_message_id: msg_120
    first_kept_message_id: msg_121
    
    tokens_before: 82000
    tokens_after: 18000
    created_at: 2026-06-03T10:00:00+09:00
    
    goal:
      user_goal: "..."
      current_task: "..."
      acceptance_criteria:
        - "..."
    
    constraints:
      hard:
        - "..."
      soft:
        - "..."
      user_preferences:
        - "..."
    
    progress:
      done:
        - "..."
      in_progress:
        - "..."
      blocked:
        - "..."
    
    key_decisions:
      - decision: "..."
        rationale: "..."
        evidence_refs: ["event_12", "tool_8"]
    
    facts:
      - fact: "..."
        confidence: 0.91
        source_refs: ["msg_33", "file_abc"]
    
    files:
      read:
        - path: "src/foo.ts"
          reason: "..."
      modified:
        - path: "src/bar.ts"
          change_summary: "..."
    
    tools:
      important_results:
        - tool_call_id: "tool_123"
          summary: "..."
          artifact_ref: "artifact_789"
    
    errors_and_retries:
      - error: "..."
        resolution: "..."
    
    open_questions:
      - "..."
    
    next_steps:
      - "..."
    
    risks:
      - "..."
    
    provenance:
      source_event_ids: ["event_1", "event_2"]
      source_message_ids: ["msg_001", "msg_120"]
      summary_prompt_version: "v3"
    

Pi 的默认 summary 格式也围绕 Goal、Constraints & Preferences、Progress、Key Decisions、Next Steps、Critical Context、read-files、modified-files 等字段组织,并且会在 compaction / branch summarization 中累计跟踪文件操作。(GitHub) Claude Code 的 agent loop 文档也建议在 CLAUDE.md 中给 compactor 明确保留项,例如当前任务目标、验收标准、读写文件路径、测试结果、错误消息和决策。(Claude Code)


7.4 压缩质量检查

建议压缩后跑 verifier:

type CompressionCheck = {
      preserves_goal: boolean;
      preserves_constraints: boolean;
      preserves_current_plan: boolean;
      preserves_open_tool_pairs: boolean;
      preserves_file_state: boolean;
      preserves_user_preferences: boolean;
      has_source_refs: boolean;
      estimated_information_loss: "low" | "medium" | "high";
    };
    

硬规则:

如果 summary 没有 current_task,压缩失败
    如果 summary 没有 next_steps,压缩失败
    如果压缩范围里有 file write,但 summary 没有 modified files,压缩失败
    如果压缩范围里有 tool error,但 summary 没有 errors/retries,压缩失败
    如果 tool call/result pair 被拆,压缩失败
    

8. 推理链路:保持可审计 Trace

Agent 的推理链路应该这样建:

UserPromptSubmitted
      → ContextBuilt
        → ModelRequestStarted
          → ModelResponseReceived
            → ToolCallRequested
              → ApprovalRequested
                → ApprovalGranted
                  → ToolCallStarted
                    → ToolCallFinished
                      → StateUpdated
                        → VerifierFinished
                          → AssistantMessageCreated
    

每个节点都有:

event_id
    parent_event_id
    run_id
    message_id
    tool_call_id
    artifact_id
    source_refs
    

这样系统可以回答:

agent 为什么调用这个工具?
    这个结论来自哪个文件?
    哪个用户约束在生效?
    哪次 compaction 后丢了信息?
    哪个 subagent 的结果影响了主线决策?
    哪个 verifier 没通过?
    

这条链路是 agent 的“外部化理性”。它不是模型私有思维链,而是工程系统可记录、可审计、可回放的行动逻辑。


9. Branch 与 Subagent:上下文隔离机制

复杂任务天然不是线性的。Agent 经常会:

尝试方案 A
    失败
    回退
    尝试方案 B
    开 subagent 做研究
    从旧状态 fork
    比较两个结果
    

所以 session 应该天然支持树结构:

root
     ├── main path
     ├── branch A
     ├── branch B
     └── subagent task
    

推荐抽象:

type SessionNode = {
      node_id: string;
      session_id: string;
      parent_node_id?: string;
    
      event_id: string;
      message_id?: string;
    
      branch_type: "main" | "fork" | "subagent" | "what_if";
      branch_summary?: string;
    
      created_at: string;
    };
    

Pi 的 session 格式直接支持树结构,entry 通过 id / parentId 连接,从而可以在一个 JSONL 文件里实现原地 branching。(Pi) Claude Code 的 subagent 设计也体现了上下文隔离:subagent 在自己的 context window 中工作,只返回 summary;官方文档明确说适合把会淹没主对话的搜索结果、日志、文件内容放到 subagent 自己的上下文里处理。(Claude Code) Claude Code SDK 的 agent loop 文档也说明,subagent 不看 parent turns,最终响应作为 tool result 返回,主 agent 的 context 增长的是 summary 而不是完整 subtask transcript。(Claude Code)

一句话:

分支是探索隔离,subagent 是上下文隔离。


10. 工程落地架构

10.1 本地 CLI 版本

适合 Codex / Claude Code / Pi 这类终端 coding agent:

~/.agent/
      sessions/
        sess_abc.jsonl
        sess_def.jsonl
    
      artifacts/
        artifact_123.txt
        artifact_456.diff
        command_789.log
    
      memory/
        user.jsonl
        project.jsonl
        workspace.jsonl
    
      indexes/
        vector.sqlite
    

MVP 可以先用 JSONL:

{"type":"SessionStarted","event_id":"evt_001","session_id":"sess_1","seq":1,"actor":"system","payload":{"agent_id":"coding-agent"}}
    {"type":"UserPromptSubmitted","event_id":"evt_002","session_id":"sess_1","seq":2,"actor":"user","payload":{"message_id":"msg_1"}}
    {"type":"ContextBuilt","event_id":"evt_003","session_id":"sess_1","seq":3,"actor":"system","payload":{"used_tokens":18420,"included":["summary","recent_messages","project_memory"]}}
    {"type":"ToolCallStarted","event_id":"evt_004","session_id":"sess_1","seq":4,"actor":"tool","payload":{"tool_call_id":"tool_1","tool_name":"read_file"}}
    {"type":"ToolCallFinished","event_id":"evt_005","session_id":"sess_1","seq":5,"actor":"tool","payload":{"tool_call_id":"tool_1","artifact_id":"art_1"}}
    

10.2 服务端版本

推荐存储:

Postgres:
      sessions
      events
      messages
      state_snapshots
      compactions
      memories
      runs
      tool_calls
    
    Object Storage:
      tool outputs
      files
      diffs
      screenshots
      logs
      datasets
    
    Vector DB:
      memory embeddings
      artifact summaries
      project docs
      code chunks
    

简化表结构:

CREATE TABLE sessions (
      session_id TEXT PRIMARY KEY,
      user_id TEXT,
      workspace_id TEXT,
      project_id TEXT,
      agent_id TEXT NOT NULL,
      status TEXT NOT NULL,
      root_session_id TEXT,
      parent_session_id TEXT,
      model_config JSONB NOT NULL,
      context_config JSONB NOT NULL,
      permission_config JSONB,
      created_at TIMESTAMPTZ NOT NULL,
      updated_at TIMESTAMPTZ NOT NULL
    );
    
    CREATE TABLE events (
      event_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL REFERENCES sessions(session_id),
      run_id TEXT,
      parent_event_id TEXT,
      seq BIGINT NOT NULL,
      type TEXT NOT NULL,
      actor TEXT NOT NULL,
      payload JSONB NOT NULL,
      causality JSONB,
      visibility TEXT NOT NULL,
      created_at TIMESTAMPTZ NOT NULL,
      UNIQUE(session_id, seq)
    );
    
    CREATE TABLE messages (
      message_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL REFERENCES sessions(session_id),
      run_id TEXT,
      parent_message_id TEXT,
      role TEXT NOT NULL,
      content JSONB NOT NULL,
      tool_call JSONB,
      tool_result JSONB,
      usage JSONB,
      context_policy JSONB NOT NULL,
      provenance JSONB NOT NULL,
      created_at TIMESTAMPTZ NOT NULL
    );
    
    CREATE TABLE state_snapshots (
      snapshot_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL REFERENCES sessions(session_id),
      source_event_id TEXT NOT NULL,
      version BIGINT NOT NULL,
      state JSONB NOT NULL,
      created_at TIMESTAMPTZ NOT NULL
    );
    
    CREATE TABLE compactions (
      compaction_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL REFERENCES sessions(session_id),
      from_message_id TEXT,
      to_message_id TEXT,
      first_kept_message_id TEXT,
      summary_message_id TEXT,
      tokens_before INT,
      tokens_after INT,
      prompt_version TEXT,
      quality_check JSONB,
      created_at TIMESTAMPTZ NOT NULL
    );
    

10.3 ContextManager 接口

推荐抽象:

interface ContextManager {
      observe(event: AgentEvent): Promise<void>;
    
      project(session: SessionRef): Promise<AgentState>;
    
      retrieve(query: ContextQuery): Promise<ContextCandidate[]>;
    
      compile(input: CompileInput): Promise<ContextBundle>;
    
      compact(scope: CompactScope, policy: CompactPolicy): Promise<Compaction>;
    
      explain(bundle: ContextBundle): Promise<ContextExplanation>;
    
      replay(session: SessionRef, target?: EventRef): Promise<ReplayResult>;
    
      resume(sessionId: string, branchId?: string): Promise<AgentSession>;
    }
    

避免只做这种接口:

getMessages(sessionId)
    appendMessage(sessionId, message)
    summarizeMessages(sessionId)
    

前者是在建 agent runtime,后者只是在管理聊天数组。


11. 最小可行版本 MVP

如果你要先落地,我建议 MVP 先做这些:

sessions
    events
    messages
    state_snapshots
    compactions
    artifacts
    

先不要急着做:

复杂向量 memory
    多 agent 编排
    高级知识图谱
    自动 branch pruning
    复杂 eval 平台
    

MVP 的核心能力:

1. 每次用户输入写入 message + event
    2. 每次工具调用写入 event
    3. 大型工具输出写 artifact,message 只放摘要和 ref
    4. 每次模型调用前构建 ContextBundle
    5. 超过 token 阈值时生成 structured summary
    6. 保证 tool call/result pair 不被截断
    7. 支持 resume
    8. 支持导出 JSONL
    9. 支持 /context 查看当前上下文构成
    10. 支持 /compact 手动压缩
    

12. 工程不变量:直接写成测试

这些不变量比字段更重要:

Invariant 1:
      Every tool_result must have a preceding tool_call.
    
    Invariant 2:
      No pending tool_call can be removed by compaction.
    
    Invariant 3:
      Every compaction summary must include source message/event range.
    
    Invariant 4:
      Every file write event must be represented in state.artifacts or summary.files.modified.
    
    Invariant 5:
      Current user request, active goal, and open questions must always be included in context.
    
    Invariant 6:
      ContextBuilder output must be deterministic given same session state and retrieval result.
    
    Invariant 7:
      Raw event log is append-only.
    
    Invariant 8:
      Summary cannot overwrite raw transcript.
    
    Invariant 9:
      Branch must not mutate ancestor branch.
    
    Invariant 10:
      Memory writes require source refs.
    
    Invariant 11:
      Long-term memory must have scope and expiry/update policy.
    
    Invariant 12:
      ContextBundle must be explainable: every included segment should have reason and source.
    

13. 反模式

反模式一:messages[] 即世界

所有历史都存在 messages 里。
    

问题:

难恢复
    难压缩
    难审计
    难分支
    难解释
    难做权限控制
    

反模式二:summary 覆盖历史

压缩后只保留 summary,不保留原始记录。
    

问题:

无法回放
    无法纠错
    无法验证
    不知道 summary 丢了什么
    

反模式三:memory 无作用域

所有记忆都进一个向量库。
    

问题:

项目污染
    用户污染
    临时事实长期化
    旧知识误用
    敏感信息扩散
    

反模式四:工具结果直接塞上下文

命令输出、文件全文、日志全部塞进 prompt。
    

问题:

token 爆炸
    噪声过多
    重点丢失
    安全风险增加
    

反模式五:压缩只做自然语言总结

“请总结上面对话。”
    

问题:

目标可能丢
    约束可能丢
    文件状态可能丢
    错误和决策可能丢
    下一步可能丢
    

反模式六:把 prompt 当 policy

只在 system prompt 里说不要做危险事。
    

问题:

不可验证
    不可强制
    不可审计
    不可测试
    

确定性规则应该进入 policy、permission、hook、validator,而不只是 prompt。Claude Code 文档也明确说,CLAUDE.md 和 auto memory 是 context,不是强制配置;要阻止动作应使用 PreToolUse hook。(Claude Code)


14. 典型项目落地对比

这里把“开源框架/工具”和“公开产品实践”分开看。Codex CLI、Pi、Pydantic AI、LangGraph、AutoGen、LlamaIndex 是更接近开源框架或开源工具的样本;Claude Code 是公开文档非常完整的商业产品实践,适合作为设计参照,但不把它归为开源框架。

项目 类型 Context / Session 落地 压缩 / 裁剪 Memory / State Trace / Hooks / 分支 适合借鉴什么
OpenAI Codex CLI 开源本地 coding agent;GitHub repo 显示 Apache-2.0 license。(GitHub) 本地保存 transcript,支持 codex resume,恢复时保留 transcript、plan history、approvals。(OpenAI开发者) Slash commands 可在 session 中切换模型、权限、查看状态、总结长对话等。(OpenAI开发者) AGENTS.md 自动加载到上下文,适合放 repo layout、build/test/lint、工程约定、done criteria 等;支持 global、repo、subdir 层级,越具体越优先。(OpenAI开发者) Hooks 覆盖 SessionStartPreToolUsePostToolUsePreCompactSubagentStartStop 等事件,并有 trust review;subagent 文档强调把噪声工作移出主线程。(OpenAI开发者) 本地 transcript、resume、project instructions、hooks、subagent、权限控制。
Claude Code 商业产品/SDK,公开文档完整,可作为产品实践参照。 Transcript 存为 JSONL,每行是 message、tool use 或 metadata,可 /export。(Claude Code) 支持 compaction;文档建议通过 CLAUDE.md 给 compactor 明确保留目标、验收标准、读写文件、测试结果、错误、决策。(Claude Code) CLAUDE.md + auto memory 两套系统;auto memory 会记录纠正、偏好、模式,并在每个 session 加载。(Claude Code) Hooks 提供 deterministic control;subagent 在独立 context 中工作,只返回 summary,减少主上下文污染。(Claude Code) 产品级 context hygiene、memory hierarchy、compaction instructions、subagent isolation、hooks enforcement。
Pi Agent 开源 AI agent harness / coding agent。GitHub repo 描述其包含 coding agent CLI、agent runtime、multi-provider LLM API。(GitHub) Session 是 JSONL,entry 通过 id / parentId 形成树,支持原地 branching。(Pi) 有 compaction 和 branch summarization;保存 CompactionEntryfirstKeptEntryIdtokensBefore,reload 时使用 summary + kept messages。(GitHub) Summary 格式包含 Goal、Constraints、Progress、Key Decisions、Next Steps、Critical Context、read-files、modified-files。(GitHub) 支持 branch summary,custom extension 可拦截 compaction/tree navigation 并自定义 summary。(GitHub) JSONL session tree、可审计 compaction、branch summary、文件操作跟踪、extension state。
Pydantic AI Python agent framework。 支持 all_messages()new_messages(),可把 message_history 传给下一次 run。(Pydantic) ProcessHistory 可在每次 model request 前修改 history,用于隐私过滤、降低 token 成本、自定义处理。(Pydantic) run_id 区分单次 agent run,conversation_id 跨多轮共享,并可通过 conversation_id='new' fork。(Pydantic) 更偏 typed message 和 observability,而不是完整 coding session tree。 typed message history、run/conversation IDs、history processor pipeline、JSON 序列化。
LangGraph 图式 agent/workflow framework。 内置 persistence,graph state 在每个 super-step checkpoint,按 thread 组织。(LangChain 文档) 支持 trim、delete、summarize messages 等 memory 管理方式。(LangChain 文档) 短期记忆是 agent state 的一部分,使用 checkpointer 持久化;长期记忆跨 session,按 namespace 保存。(LangChain 文档) Checkpoint 支持 human-in-the-loop、time travel、fault tolerance 和从旧 checkpoint fork。(LangChain 文档) 状态 checkpoint、thread-scoped memory、time travel、fault-tolerant agent execution。
AutoGen 多 agent framework。 更偏 agent chat 与 multi-agent 协作。 通过 memory protocol 在每个 step 前检索并注入相关信息。(Microsoft GitHub) Memory protocol 包含 queryupdate_contextaddclearcloseListMemory 是简单可预测的顺序记忆实现。(Microsoft GitHub) 更适合多 agent 协作中的 memory 注入,而不是 session tree。 Memory protocol、RAG-style context injection、多 agent 上下文共享。
LlamaIndex Agents Context-aware agent / RAG framework。 Agent run 可以传入 memory。 默认 memory 可按 token limit 提供最近消息;新的 Memory class 更灵活。(Developer Documentation) Memory 支持 put() / get(),也支持短期 FIFO 和可选长期记忆抽取。(Developer Documentation) 更偏 RAG、memory、agent workflow。 Memory 抽象、短期/长期结合、context-aware RAG agent。

这些系统虽然侧重点不同,但正在收敛到几个共同模式:

可恢复 session
    项目级 context 外置
    显式或自动 compaction
    typed messages
    state / checkpoint
    memory scope
    event / hook lifecycle
    branch / fork / subagent isolation
    context inspection / trace
    

这说明这些抽象不是某个项目的偶然实现,而是 agent 系统复杂化后的自然结果。


15. 推荐的最终范式

可以把 Context Manager 的最终范式总结成十句话:

1. Session 是工作容器,不是上下文本身。
    
    2. Event 是事实源,必须 append-only、可审计、可回放。
    
    3. Message 是通信记录,不是完整世界模型。
    
    4. State 是从事件、消息、artifact、memory 投影出的当前工作态。
    
    5. Memory 是经过治理的跨时间知识,不是随手缓存。
    
    6. Artifact 保存完整证据,context 只放摘要、关键片段和引用。
    
    7. Trace 保存目标、假设、证据、决策、行动、验证,而不是隐藏思维链。
    
    8. ContextBundle 是一次模型调用前编译出的临时视图。
    
    9. Compression 是有损语义蒸馏,必须保留 provenance 和质量检查。
    
    10. Policy、permission、hook、validator 是确定性边界,不能只靠 prompt。
    

16. 最后给一版文章结尾

可以直接用:

Agent Context Management 的本质,不是把更多历史塞进模型,也不是在上下文窗口满了之后做摘要。它是一个围绕目标、状态、证据、记忆、权限和预算的信息治理系统。

一个成熟的 Context Manager 应该把 raw events、messages、artifacts 作为事实源,把 state、summary、memory 作为可重建的语义投影,把每次模型输入视为临时编译产物,并通过 trace 保持 agent 行动的可解释性。

未来模型会变化,tool calling 协议会变化,context window 会变化,记忆系统会变化。但只要 agent 仍然需要在有限注意力、有限权限和不完整信息下行动,Context Manager 就必须解决同样的问题:什么信息应该被保留、被压缩、被检索、被展示、被遗忘,以及为什么。

因此,Context Manager 不是聊天历史管理器,而是 agent 的注意力、记忆和行动边界的操作系统。