Hotdry.

Article

长时运行 Coding Agents 的内存管理:需求分页与增量清理工程实践

针对长时运行 coding agents 的内存泄漏问题,解析 Remoroo 等项目的需求分页机制与增量清理策略的工程实现细节。

2026-04-18ai-systems

当一个 coding agent 需要连续运行数小时甚至数天来处理复杂任务时,内存管理便成为决定系统能否持续稳定工作的关键因素。与传统服务端进程不同,AI agent 的工作负载具有高度动态性 —— 每一次工具调用、每一次日志记录、每一次状态更新都会产生新的上下文数据。如果不加控制地累积这些数据,内存占用将随时间线性增长,最终导致进程崩溃或被容器调度器强制终止。这种现象在业界被称为「上下文漂移」(context drift),是长时运行 agents 面临的核心工程挑战。

需求分页机制的设计哲学

Remoroo 作为新兴的自主实验引擎,采用了需求分页(demand-paging)内存管理策略来解决这一问题。其核心理念源自操作系统虚拟内存的启发:不在内存中永久保留所有上下文数据,而是仅保留当前任务最相关的「工作集」(working set),将历史数据以页面形式存储在磁盘或分布式缓存中,待需要时再按需调入。这种设计有几个关键优势。首先,它从根本上限制了运行时内存的峰值,避免了无界增长带来的 OOM 风险。其次,它通过显式管理上下文生命周期,使开发者能够精确控制何时加载、何时卸载特定数据,从而在内存效率与访问延迟之间取得平衡。

具体实现层面,Remoroo 将 agent 的完整执行轨迹划分为多个离散的「记忆单元」,每个单元包含特定的规格说明、基线结果、实验日志和代码差异。系统会根据当前任务的语义相关性动态决定保留哪些单元在内存中,其余单元则被序列化后写入持久化存储。当 agent 需要引用历史信息时,系统通过轻量级检索机制从存储层拉取对应的记忆单元,并将其注入到当前的工作上下文。这种机制使得 agent 能够在数天的连续运行中保持「记忆连贯性」,同时不会因为历史数据的累积而耗尽内存资源。

内存泄漏检测的分层策略

尽管需求分页从架构层面缓解了内存无界增长的问题,但在实际工程实践中,仍需配套的泄漏检测机制来捕获那些「漏网之鱼」。内存泄漏的来源通常包括:未正确释放的工具输出缓存、未关闭的文件描述符、数据库连接池中的泄漏连接、静态单例中的无限增长对象,以及线程本地存储中未能清理的上下文。检测这些问题的策略可以分为三个层次。

第一层是运行时监控。通过在 staging 环境中部署轻量级内存分析器,持续采集进程的堆内存快照和 RSS(Resident Set Size)指标。建议每 6 至 24 小时进行一次增量快照对比,观察哪些对象类型或缓存实例的存活数量呈持续上升趋势。典型的泄漏模式表现为特定类的实例计数随运行时间线性增长,而正常对象应该服从某种稳定分布或随 GC 周期波动。

第二层是路径追踪。结合分布式 tracing 工具(如 OpenTelemetry),将内存分配与特定的代码路径关联。例如,若发现某个工具输出缓存持续增长,则需要定位是哪些工具调用写入了该缓存、是否有对应的读取或过期逻辑。这一层通常需要开发团队在关键路径上埋入分配计数器,并在问题暴露后通过火焰图或堆转储文件定位根因。

第三层是自动化告警。将内存增长率纳入生产监控的关键指标,当 RSS 或堆使用量的增长速度超过预设阈值时触发告警。建议设置两级阈值:警告级(warning)用于提醒运维人员关注,严重级(critical)用于触发自动扩容或重启策略。阈值的选择应基于基线测试数据,一般而言,健康的长时间运行进程应该呈现平稳或略有波动的内存曲线,而非持续上升的线性趋势。

增量清理的工程实现

一旦检测到内存泄漏,就需要实施清理策略。与其等待泄漏累积到不可收拾的地步后再进行大规模垃圾回收,不如采用增量清理的方式,将清理工作分散到正常运行过程中,从而将对服务延迟的影响降至最低。

增量清理的第一层是立即式资源释放。对于缓存、连接池等显式管理的资源,应实现有界淘汰策略。一个有效的做法是采用 LRU(最近最少使用)缓存结构,并设置最大容量限制和单项最大存活时间。例如,将工具输出缓存的最大条目数设为 1024 条或最大存活时间设为 24 小时(以先到者为准),超过限制时显式调用清理回调释放底层资源。这种策略在 Python 中可以使用 functools.lru_cache 配合弱引用,或自行实现带过期策略的装饰器。

第二层是软引用与后台清理。对于不适合显式淘汰的对象,可以利用语言层面的弱引用或软引用机制让 GC 在内存压力增大时自动回收。配合定时任务(如 Python 的 schedule 库或 Go 的 time.Ticker),每隔一定周期扫描并清理已失效的引用。需要注意的是,这种方式虽然对代码侵入性较小,但清理时机不可控,可能导致在内存紧张时才触发回收,增加 GC 暂停时间。

第三层是进程级冷启动。当上述两层策略均无法有效控制内存增长时,需要考虑进程的热重启或冷启动策略。许多生产系统采用「滚动重启」机制:启动新进程接收流量,旧进程在处理完当前请求后优雅退出。这种策略的关键在于确保状态能够正确持久化并在重启后恢复。Remoroo 的设计使得这种重启对上层逻辑透明 —— 因为所有上下文都存储在外部存储层,进程重启仅仅是重新加载必要的记忆单元。

工程落地的关键参数

将上述策略落地到具体项目时,需要关注几个可配置的工程参数。内存上限方面,建议将容器或进程的内存限制设置为基线测试中正常值的 1.5 至 2 倍,留出足够的缓冲空间应对突发流量。快照频率方面,生产环境建议每 12 小时进行一次增量堆快照,测试环境可以加密到每小时一次以加速问题发现。缓存淘汰方面,工具输出缓存的建议容量为 512 至 2048 条,具体取决于单条输出的大小;基线结果缓存由于读取频率较高,可以适当放宽到 4096 条。

监控指标方面,需要重点关注 RSS 增长率(每小时不超过基线的 5%)、GC 暂停时间(Python 中关注 gc.collect 的耗时,Go 中关注 GOGC 参数的变化)以及活跃连接数(数据库连接、HTTP 连接池的使用率)。建议将这些指标接入 Prometheus 或类似的时序数据库,并配置 Grafana 面板进行可视化。

验证清理策略有效性的最终标准是:在连续运行 72 小时以上的情况下,内存曲线应趋于平稳,不再出现持续上升的趋势。可以通过对比清理策略上线前后的内存曲线,或使用 A/B 测试框架对不同清理参数进行对比实验来验证效果。


资料来源:本文关于 Remoroo 需求分页机制的信息来源于 Remoroo 官方产品介绍和 LinkedIn 技术分享;增量清理策略的工程实践参考了 QA Brains 的内存泄漏测试指南、OneUptime 的长时运行服务内存管理最佳实践,以及 ACM 上关于增量垃圾回收的学术研究。

ai-systems