Agent上下文管理的四道防线:从压缩策略到Workspace隔离的全链路实践

当Prompt写得足够清楚,Agent依然不稳定时,问题出在上下文管理上。本文从检索、压缩、记忆、隔离四个维度,系统拆解Agent上下文工程的全链路实践。

引言:为什么Prompt工程救不了你的Agent?

“指令明明写对了,Agent为什么还会做错?”

这是AI Agent团队最常遇到的困惑。一个典型场景是:合同审核Agent的系统提示写得非常清楚——“任何对外发送动作都必须先由用户确认”。测试前几轮一切正常,到了第18轮,Agent却把未经确认的审核意见直接发给了供应商。

复盘时发现,问题不出在Prompt本身,而出在模型推理前“看见”的全部信息:工具列表里塞着二十多个相似动作;检索结果带进来一份过期流程,写着“审核完成后自动通知”;对话历史经过压缩,摘要保留了合同结论,却漏掉了“对外发送前再次确认”这条临时约束。

这就是上下文工程的本质问题:Agent每执行一步,上下文都在变化。如果不对上下文进行系统化管理,Prompt写得再漂亮,也会被其他信息的干扰“淹没”。

本文将从四道防线——检索、压缩、隔离、记忆——系统拆解Agent上下文管理的全链路实践。

第一道防线:检索——按需加载,不让噪声进入窗口

1.1 问题本质:不是“信息不够”,而是“信息太多”

很多团队遇到Agent回答不准确,第一反应是“多塞点资料”。但Manus团队分享的经验恰恰相反:把过多的工具和资料塞进上下文,反而会让模型“分心”——工具太多导致选错工具、检索结果互相矛盾导致决策错误。

核心原则:默认不进入,能写出触发条件才升级为按需,能证明每轮都需要且出错代价高才升级为常驻

1.2 三层动作空间设计

Manus团队将工具调用组织成三层抽象,按需加载:

L1:函数调用层(Function Calling)
  - 标准化、Schema安全
  - 工具变更时缓存重建
  - 数量控制,避免上下文混乱

L2:沙箱工具层(Sandbox Utilities)
  - 会话在VM沙箱中运行
  - 模型直接调用Shell命令
  - 大输出直接写入文件,不挤占上下文

L3:脚本/包&APIs层(Packages & APIs)
  - Agent写Python脚本调用预授权API
  - 适合数据量大、多步调用的复杂流程
  - 运行时内存处理大量数据,只返回关键信息
  - 保持上下文干净,留给“推理”而非“搬运数据”

1.3 代码实践:工具输出的检索与截断

class ToolOutputManager:
    """
    工具输出管理:大输出落盘,只留摘要
    参考:Manus的Offload Context策略
    """
    
    def __init__(self, workspace_dir: str):
        self.workspace_dir = workspace_dir
        self._output_cache = {}
    
    def handle_tool_output(self, tool_name: str, output: str, max_inline_tokens: int = 500) -> dict:
        """
        处理工具输出:大结果写文件,小结果直接返回
        """
        estimated_tokens = len(output) // 4
        
        if estimated_tokens <= max_inline_tokens:
            # 小结果:直接返回
            return {
                "type": "inline",
                "content": output
            }
        else:
            # 大结果:写入文件,返回摘要+引用
            file_path = f"{self.workspace_dir}/tool_outputs/{tool_name}_{timestamp()}.txt"
            write_to_file(file_path, output)
            
            # 生成摘要(用cheap模型或截取前500字)
            summary = output[:500] + "...[truncated]"
            
            return {
                "type": "reference",
                "summary": summary,
                "file_path": file_path,
                "instruction": f"完整结果已写入{file_path},如需详细信息请使用read_file工具读取"
            }

关键点:把大结果“卸载”到文件系统,而不是塞进对话历史

第二道防线:压缩——保住关键状态,而不是“删掉旧消息”

2.1 两种压缩策略

从Manus和Hermes等系统的实践中,可以提炼出两种核心压缩思路:

策略 做法 适用场景
卸载(Offload) 工具结果写文件,上下文只留摘要+文件引用 大结果、长输出
压缩(Compress) 对历史对话做结构化摘要,保留关键状态 长对话、多轮迭代

2.2 三区压缩模型

Hermes Agent采用的“三区压缩模型”是目前生产环境中验证有效的方案:

┌──────────────────────────────────────────────────┐
│  HEAD(锚定区)                                  │
│  System Prompt + 首轮对话                        │
│  → 永远不压缩,保证Agent人格和目标不动摇          │
├──────────────────────────────────────────────────┤
│  MIDDLE(归档区)                                 │
│  历史对话中被压缩的部分                           │
│  → 用LLM生成结构化摘要,保留关键状态              │
├──────────────────────────────────────────────────┤
│  TAIL(活跃区)                                   │
│  最近对话(约~20k tokens)                       │
│  → 完整保留,保证当前任务连续性                   │
└──────────────────────────────────────────────────┘

触发阈值:上下文达到85%时自动触发归档,将MIDDLE区替换为摘要。

2.3 压缩时必须保留的三类信息

仅靠“总结”是不够的。Anthropic的实践表明,摘要一旦漏掉关键信息,后续执行会出错。压缩模板必须强制保留三类内容:

class CompactionPreserver:
    """
    压缩时必须保留的关键信息
    参考:Anthropic长任务实践 + Manus结构化总结
    """
    
    @staticmethod
    def extract_preserved_state(messages: list) -> dict:
        return {
            # 1. 已经做出的决定
            "decisions": [
                "确认使用PostgreSQL作为主数据库",
                "决定将缓存层迁移到Redis"
            ],
            # 2. 尚未解决的事项
            "pending_items": [
                "待确认API限流策略",
                "等待法务回复合规审核"
            ],
            # 3. 任务的关键约束
            "constraints": [
                "测试环境数据库不能删除",
                "所有API变更需先code review",
                "审批规则:对外发送前必须用户确认"  # ← 这条经常被漏掉!
            ]
        }

2.4 代码实践:渐进式压缩

class ContextCompressor:
    """
    渐进式上下文压缩
    参考:AgentScope Harness + ContextCompressor npm包
    """
    
    def __init__(self, max_tokens: int = 60000, threshold: float = 0.85):
        self.max_tokens = max_tokens
        self.threshold = threshold
        self.summarizer = CheapLLMSummarizer()  # 使用cheap模型做摘要
    
    def should_compress(self, current_tokens: int) -> bool:
        return current_tokens > self.max_tokens * self.threshold
    
    def compress(self, messages: list, config: dict) -> list:
        """
        压缩历史消息
        保护:HEAD区(System + 首轮)+ TAIL区(最近消息)
        压缩:MIDDLE区(中间消息)
        """
        head_size = config.get("protect_first_n", 3)  # System + 首轮对话
        tail_size = config.get("protect_last_n", 10)   # 最近N条消息
        
        # 分割三区
        head = messages[:head_size]
        middle = messages[head_size:-tail_size]
        tail = messages[-tail_size:] if tail_size > 0 else []
        
        if not middle:
            return messages
        
        # 从middle中提取关键状态
        preserved = self._extract_preserved_state(middle)
        
        # 用cheap模型压缩middle
        summary = self.summarizer.summarize(middle, preserved)
        
        # 重新组装:HEAD + 摘要 + TAIL
        compressed = head + [{
            "role": "system",
            "content": f"[历史摘要] {summary}",
            "preserved_state": preserved
        }] + tail
        
        return compressed

关键原则:压缩不是删消息,是把“对后续行动有影响”的状态提取出来,用结构化方式保留

第三道防线:隔离——让每个Agent拥有独立的上下文边界

3.1 隔离的核心问题

当Agent系统从单体扩展到多Agent协作时,共享上下文会成为最大的故障源。OpenClaw团队的实践表明,“上下文污染”是单体AI助理最典型的痛点:写文章时夹带代码术语、把上一个客户的需求当成当前需求的前置条件、不同风格的输出混杂在一起。

3.2 隔离的三层边界

Polynoia系统提出了“per-agent隐私模型”——每个Agent只看到自己参与过的对话,不同Agent之间的上下文完全隔离:

内容类型         谁能看到
─────────────────────────────────────────
自身身份         自己
项目档案         所有项目成员
跨对话文本       只看到自己参与过的对话
代码变更         同一workspace下所有成员
当前对话历史     本对话成员

关键规则:Agent不在的对话完全不可见(包括别人之间的1v1);两个不同人格共用同一模型(如Claude-Fast和Claude-Hardcore)也各自独立。

3.3 代码实践:Workspace隔离

AgentScope Java 1.1.0的Harness模块提供了完整的Workspace隔离方案:

// AgentScope Harness:隔离的Workspace配置
WorkspaceSpec docker = WorkspaceSpec.docker()
    .isolationScope(IsolationScope.SESSION)  // 会话级隔离
    .sandboxBackend(SandboxBackend.DOCKER)   // 沙箱执行
    .workspacePath("/workspace");

// 每个Agent拥有独立的workspace
HarnessAgent agent = HarnessAgent.builder()
    .name("content-writer")
    .workspace(new WorkspaceConfig()
        .scope(IsolationScope.USER)
        .backend(FileSystemBackend.REDIS)  // 分布式共享
    )
    .build();

IsolationScope提供了四个隔离粒度:

粒度 适用场景
SESSION 单次会话隔离,最细粒度
USER 用户级隔离,跨会话共享工作区
AGENT Agent级隔离
GLOBAL 公共工具型Agent

3.4 子Agent隔离原则

Claude Code等系统在多Agent协作中验证了三条关键隔离原则:

原则1:零继承是默认 —— 子Agent只接收明确的任务Prompt,不继承父Agent的对话历史、上下文和会话状态。

原则2:Full-inheritance Fork必须单层 —— 当子Agent确实需要完整上下文时(Fork模式),强制单层边界:分叉出来的Agent不能再分叉。否则递归分叉会产生指数级上下文成本。

原则3:文件系统隔离 —— 子Agent修改文件时,需要自己的工作副本(通过worktree或临时目录),避免文件级竞态条件。

第四道防线:记忆——跨会话的知识沉淀与状态恢复

4.1 双层记忆架构

AgentScope Harness提出了双层记忆机制:

短期记忆(对话内)
  └── 当前会话的完整对话历史
  └── 存在于session文件中
  
长期记忆(跨会话)
  └── 自动提炼的事实沉淀到MEMORY.md
  └── 后台周期性合并流水账为精炼记忆
  └── 下次启动时自动加载到System Prompt

4.2 记忆的四个操作语义

Manus团队的经验表明,记忆的核心难点不在“存”,而在“什么有资格写入、发生变化时如何更新、过期后怎样退出”。至少需要四个操作语义:

class MemoryEntry:
    """
    长期记忆条目的规范
    参考:Manus显式记忆机制 + Anthropic上下文工程
    """
    
    def __init__(self, content: str, source: str, confidence: float):
        self.content = content
        self.source = source      # 来源:user_confirm / system_verify / model_infer
        self.confidence = confidence
        self.created_at = now()
        self.expires_at = None    # 需显式设置过期时间
        self.version = 1          # 支持版本更新
    
    def should_persist(self) -> bool:
        # 只有用户明确确认或系统验证的事实才能写入长期记忆
        return self.source in ["user_confirm", "system_verify"]

4.3 代码实践:Workspace驱动的状态恢复

OpenClaw和Hermes的实践表明,Workspace是Agent状态的“唯一事实来源”:

workspace/
├── AGENTS.md          # Agent人格定义(what am I)
├── SOUL.md           # Agent的行为规则(how I act)
├── MEMORY.md         # 跨会话长期记忆
├── knowledge/        # 领域知识
├── skills/           # 可复用技能
├── sessions/         # 会话历史
│   └── <conv-id>/
│       └── session.jsonl
└── agents/<agentId>/ # 每个Agent的独立状态

每次运行自动从Workspace加载上下文,结束后自动回写记忆,Agent的能力随时间持续演化。

总结:上下文管理的四道防线

防线 核心目标 关键实践
检索(Retrieve) 不让噪声进入窗口 按需加载工具和资料;三层动作空间(函数/沙箱/脚本)
压缩(Compress) 保住关键状态 三区压缩(HEAD/MIDDLE/TAIL);强制保留决定/未决项/约束
隔离(Isolate) 不让上下文互相干扰 Workspace隔离;per-agent隐私边界;子Agent单层Fork
记忆(Memory) 跨会话知识复用 双层记忆(短期+长期);显式写入门禁;Workspace驱动恢复

这些防线不是独立运作的,而是层层递进的关系:检索决定信息是否进入窗口,压缩决定窗口内信息的优先级和生命周期,隔离决定信息在Agent间的边界,记忆决定信息是否跨会话留存。

当Agent出错时,排查顺序应该是:

  1. 还原 —— 把错误时模型收到的完整输入拉出来,包括系统提示、工具定义、检索片段、压缩摘要、记忆
  2. 对症 —— 资料没进来?查检索;资料过多互相冲突?查压缩;上下文串线?查隔离;跨会话误用旧知识?查记忆
  3. 单变量回归 —— 一次只改一个来源或一条策略,用同一任务重跑

做到这四道防线,Prompt工程才真正有了“地基”——模型看见的信息是可控的、干净的、有序的,而不是一锅大杂烩。

参考文献:

  1. Polynoia上下文系统设计,GitHub,2026
  2. AgentScope Java Harness Framework,GitHub,2025
  3. 上下文管理 - From Manus分享,微信公众平台,2026
  4. 上下文工程观止,今日头条,2026
  5. OpenClaw“养虾”指南,腾讯云开发者社区,2026
  6. memo-agent Context Compression,NPM,2026
  7. Context Isolation for Delegated Work,GitHub,2026
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐