← 返回
IT技术

Agent 该暂停还是每次新建?2280 个会话实测给出答案

✍️ zhirenhun 📅 2026/11/1 👁 7 阅读 ⏱ 25 分钟
Agent 该暂停还是每次新建?2280 个会话实测给出答案

我们的 Managed Agents 基准测试显示恢复耗时 540 毫秒,真实答案是 249

在 DigitalOcean Managed Agents 上实测 2,280 个会话:冷启动、恢复与命令延迟的 p50、p95 和 p99。

我们测量了 DigitalOcean Managed Agents 上 Agent 沙箱的启动、暂停、恢复和命令执行速度。2026 年 10 月,在产品公开预览期间,我们历时四天运行了 2,280 个会话。第一次测得的恢复耗时约为 540 毫秒。这个结果是错的,而它出错的方式,适用于任何通过轮询沙箱状态来判断其是否就绪的基准测试。正确测量后,恢复调用在中位数上 249 毫秒返回,且返回那一刻会话即可使用。

本文介绍我们的发现、轮询为何误导了我们,以及如果你正在决定是让 Agent 沙箱持续运行、在轮次之间暂停,还是为每个任务新建一个沙箱,这些数字意味着什么。测试框架已开源(managed_agents_review_benchmark),你可以核验每一个数字。

主要发现

所有数据均由同一区域内的一台 Droplet 客户端实测得出,环境为 mars-2vcpu-4gb 沙箱,使用 opencode Agent 适配器。

项目 p50 p95 p99 n
新会话就绪(create 调用返回) 658 ms 970 ms 2,590 ms 2,180
新会话:首条命令执行完成 904 ms 1,300 ms 2,950 ms 2,180
resume 调用返回(会话已就绪) 249 ms 427 ms 473 ms 298
恢复的会话:首条命令执行完成 493 ms 713 ms 785 ms 298
运行中会话执行命令 184 ms 368 ms 414 ms 18,740

为什么轮询测出来是 540 ms

给 resume 计时最直接的做法是:发出 resume 请求,然后轮询会话状态,直到状态变为 READY。我们的测试工具最初就是这么做的。连续三天,它测出的 resume 到就绪中位数为 538–585 ms。

测试框架还记录了每次状态转换前后的时间窗口:最后一次“未就绪”检查发出的时间,以及第一个“就绪”响应到达的时间。对于恢复操作,这个窗口在100% 的流程中都从零开始。恢复调用返回后约 100 ms 发出的第一次状态检查,就已经显示 READY。所以那 540 ms 根本不是恢复耗时,而是:

我们等待询问的整个过程中,会话其实一直处于就绪状态。

这是一个普遍限制。轮询客户端能测出的最快状态转换,不会快于一个轮询间隔加一次 API 往返。在这套环境下,这个下限约为 300 ms,比我们要测量的对象还大。用这种方式测出的两个数字,只有在轮询间隔和网络路径都相同时才具有可比性;别人基准测试里的“X ms 就绪”数据,也可能包含同样的误差。

为了准确测量恢复耗时,我们不再轮询。测试框架发出恢复请求后立即执行命令,如果 API 返回“尚未就绪”就每 20 ms 重试一次。结果一次重试都没用上。每条首条命令都成功了,耗时 192 ms,与从未暂停过的会话上执行命令的耗时(189 ms)相同。没有任何环节在等待沙箱唤醒。

新会话约 650 ms 就绪,首条命令约 0.9 s 落地

创建会话同样是同步的。在 100% 的单会话流程中,创建响应本身就报告会话已就绪。

p50 p95 p99 n
创建 → 就绪 658 ms 970 ms 2,590 ms 2,180
创建 → 首条命令 904 ms 1,300 ms 2,950 ms 2,180

中位数每天都很稳定,尾部延迟却不是:

日期(2026年) Create p50 p95 p99 n
10月21日 646 ms 2,007 ms 2,668 ms 1,286
10月23日 713 ms 938 ms 1,053 ms 298
10月26日 649 ms 885 ms 1,021 ms 298
10月27日 662 ms 895 ms 1,054 ms 298

四天里有三天,95% 的 create 操作在一秒内完成。10月21日这天,p95 约为 2 秒。只做一天基准测试,得到的 p95 会大不相同,具体取决于测的是哪一天。

同时创建五个会话并不会拖慢冷启动:中位数为 909 ms,而逐个创建是 912 ms。

关于延迟预算:从创建会话到执行第一个有效操作,按约一秒来规划。我们见过最慢的一次 create 耗时 6.0 秒,所以 create 超时时间应明显高于 p99。

暂停的会话恢复工作所需时间约为新会话的一半

下面两组数据来自同一次运行、同一个客户端、同一个小时:

p50 p95 p99
创建 → 第一条命令 912 ms 1,198 ms 1,403 ms
恢复 → 第一条命令 493 ms 713 ms 785 ms

恢复在每个百分位上都更快,快约 400–600 ms。而对大多数 Agent 来说,实际差距还要更大。暂停的会话保留着文件、已安装的软件包和工作状态,新会话则必须重新构建这些,而重建时间完全没有算进那 912 ms 里。

暂停也是更省钱的等待方式。空闲会话如果一直运行,就不再为 CPU 付费,因为 CPU 按实际用量计费,但内存仍照常收费。默认的 2 vCPU / 4 GB 沙箱约为每小时 $0.038(4 GB × 每 GB-小时 $0.0095),若每天等待八小时,一个月约 $9。暂停的会话完全不产生计算费用,只需为快照和卷的存储付费,价格为每 GiB-月 $0.05,因此 10 GiB 工作区每月约 $0.50。销毁会话在等待期间不花钱,但下一条消息到来时要承担冷启动和重建状态的代价。以上价格来自 Harness Runtime 定价页,截至 2026 年 10 月。

暂停 5 分钟后,恢复仍是同步的,只是尾部更长

真实 Agent 很少在五秒后就恢复。用户会离开。于是我们让 100 个会话各暂停五分钟,再以每批五个同时恢复。

慢恢复是成簇出现的。它们来自 20 批中的 4 批,且常常同一批里就有好几个,这说明原因更可能是同时恢复五个会话,而非暂停时间更长。本次测试无法区分两者,因此应把 5 分钟场景下的尾部视为并发恢复的上界,而不是暂停更久的代价。

运行中会话的命令约需 180 ms,约 30% 会多花约 150 ms

p50 p95 p99 n
命令往返(uname) 184 ms 368 ms 414 ms 18,740

这个分布不是一条带长尾的平滑曲线,而是分成两簇:一簇在 170 ms 左右,另一簇在 330 ms 左右。约 30% 的请求落在较慢的那一簇。每个请求变慢的概率与上一个请求无关,而且在我们测试的每一天,这个比例都差不多(28.9%–30.7%)。暂停、恢复和销毁调用也呈现同样的规律。这就是为什么本文中每个操作的 p95 大约是 p50 的两倍,而不是少数离群值拖出的长尾。

作为参照,同一台 Droplet 上发起一次简单的已认证 API 调用耗时 116–171 ms。一条命令的往返时间大部分花在 API 层,沙箱自身的工作量很小。

恢复操作不会让会话变慢:恢复后的命令中位耗时 186 ms,而暂停前是 189 ms。

这对你的 Agent 架构意味着什么

在用户轮次之间暂停,而不是销毁会话。这样能把首个动作的耗时大致减半,同时保留会话状态。

恢复后不要轮询。resume 调用返回时,会话就已经就绪。轮询只会给每次恢复平白增加至少一个轮询间隔和一次往返。下面的最小示例展示了这一模式。

按 p95 而非 p50 来规划延迟预算。一次需要 1 次恢复加 3 条命令的回复,中位耗时约 800 ms(249 + 3 × 184)。把各操作的 p95 相加,可得到约 1.5 s 的保守上限(427 + 3 × 368)。由于约 30% 的请求偏慢,多步回复通常至少会碰上一次。

按观测到的最大值而非中位数设置超时。在这组数据中,最慢的 create 耗时 6.0 s,最慢的命令耗时 4.3 s,最慢的从恢复到首条命令耗时 1.3 s。

以下场景尚未测试:

这里测的是沙箱,不是模型。当 Agent 使用 DigitalOcean 托管的模型时,模型调用经由 DigitalOcean 的 Serverless Inference,计费记在同一账户上。Agent 首次模型响应的耗时是下一项要测的内容。

轮次之间暂停且不轮询:一个最小示例

整个模式只需四次API调用:创建会话、执行命令、用户离开时暂停、下一条消息到达时恢复。创建和恢复都是同步操作,无需轮询。示例使用Python 3,依赖requests库和Harness Runtime API:

import os
import requests

API = "https://api.digitalocean.com/v2/agents/sessions"
TOKEN = os.environ["DIGITALOCEAN_TOKEN"]

def call(method, path="", headers=None, **kwargs):
    headers = {"Authorization": f"Bearer {TOKEN}", **(headers or {})}
    r = requests.request(method, API + path, headers=headers, timeout=30, **kwargs)
    r.raise_for_status()
    return r.json() if r.content else {}

# Create. Synchronous: the session is ready when this call returns.
spec = "name: demo-agent\nagent: opencode\nsize: mars-2vcpu-4gb\n"
body = call("POST", data=spec, headers={"Content-Type": "application/yaml"})
sid = body["session"]["session_id"]

def run(argv):
    """Run a one-shot command in the sandbox, without a shell.
    Returns {"exit_code": ..., "stdout": ..., "duration_ms": ...}."""
    return call("POST", f"/{sid}/sandbox/exec", json={"argv": argv})

print(run(["uname", "-a"])["stdout"])

# Pause while the user is away. Compute charges stop; state is kept.
call("POST", f"/{sid}/pause")

# ...later, when the user's next message arrives:
call("POST", f"/{sid}/resume")   # synchronous: ready when it returns
print(run(["uname", "-a"])["stdout"])  # no status polling in between

call("DELETE", f"/{sid}")        # destroy when the work is finished

你未必需要自己调用 pause:会话在闲置 15 分钟后会自动暂停,向已暂停的会话发送新消息即可恢复。同样的生命周期在 CLI 中也能用,对应命令是 doctl harness-runtime pause 和 doctl harness-runtime resume(参见如何管理会话)。

限制

测试方法

规模。四天内(2026 年 10 月 21 日、23 日、26 日和 27 日)共测量 2,298 次完整流程,另有预热试验记录后丢弃。其中 2,280 次流程完成,18 次因 HTTP 403 失败,不计入统计。

单次流程。创建会话 → 运行第一条命令 → 再运行五条 → 暂停(通过状态检查确认)→ 等待 5 秒或 5 分钟 → 恢复 → 运行第一条命令 → 再运行五条 → 销毁。每条命令都是 uname,不经过 shell 执行,因此测量的是平台开销而非工作负载。

客户端。在 RIC1 的一台 Droplet 上运行基于标准库的 Python 测试框架,调用公开的 Harness Runtime API。连接会复用,TLS 握手在所有计时器之外完成,与带连接池的 SDK 做法一致。试验按随机交错的顺序执行。失败会被计数,绝不静默丢弃。测试框架的报告为每个中位数给出百分位自助法 95% 置信区间。

速率限制。所有测试均在 API 默认限制(每小时 5,000 次请求)以内运行。测试框架在会话之间自行控制节奏,会话内部从不等待,因此没有任何计时包含刻意等待。

恢复时间的测量。本文中的恢复数据来自上文所述的非轮询方法。基于轮询的数据仅在解释其为何不准确时才会出现。

已修正。在五路并发的那次运行中,测试工具链的早期版本把连接建立时间计入了两个端到端计时器。本文数据已用工具链记录的逐请求耗时精确扣除这部分开销,公开发布的工具链也已修复该问题。

与已公布数据的一致性。Managed Agents 发布文章中报告命令耗时 189 ms、恢复耗时 305 ms。我们实测命令耗时为 184–189 ms,恢复调用为 249 ms。

复现方法

工具链、测试及完整命令行见 managed_agents_review_benchmark。

本文中的运行使用:

# cold start, commands, polling-based resume (October 21, 23, 26)
python3 ma_bench.py run --trials 300 --warmup 2 --dwell 5 --respect-default-limits

# resume without polling, 5-second pause (October 27)
python3 ma_bench.py run --trials 300 --warmup 2 --dwell 5 --resume-mode exec --respect-default-limits

# resume without polling, 5-minute pause, five sessions at once (October 27)
python3 ma_bench.py run --trials 22 --warmup 2 --dwell 300 --concurrency 5 --resume-mode exec --respect-default-limits

——

广告 广告
🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。