Agent 跑了 7 天,团队欠下了这 5 笔运维债

让一个自主 Agent 跑起来,三天就能做到。但让一个 Agent 在团队里长跑------权限收口、接管机制、记忆清洗、成本核算------这些债你一定会还,问题只是主动还还是被动还。这是我们第 7 天补的那批账单。

我很少在技术文章里聊"团队"这个词。这次要聊。

不是因为 Agent 技术变难了,而是因为我们意识到:Agent 跑久了,制造的不是 bug,是治理空白。你的代码没写错,模型没出问题,但团队里开始出现一些奇怪的对话------

"这个任务谁批的?"

"Agent 把那个配置改了,是有人让它改的吗?"

"它昨晚干了啥我能看到吗?"

这类问题的共同来源是:当初接入 Agent 的时候,我们把它当成了一个工具------像 CI/CD 一样,跑就跑,挂了重启。但它实际上更接近一个有权限的同事------它能写、能改、能发、能删,而且从不请假。

这篇是我们 openclaw-lab 运行 7 天之后,在团队层面补上的 5 笔治理债。没有宏观方法论,只有实际踩过的坑和补上的机制。


运维债 1:权限矩阵------Agent 该能做什么,你可能没认真想过

Agent 上线前,大多数团队做的权限设计是这样的:

"它需要调 API,那就给它 API Key。它要发帖,那就让它有发帖权限。"

这不是权限设计,这是权限发放。两者的区别:

  • 权限发放:把 Agent 需要用到的权限全给了,然后祈祷它不出事
  • 权限设计:把 Agent 在正常路径上需要的权限给了,并且明确了它不应该能做什么

我们在第 6 天做了一张权限矩阵,这件事花了我们一个小时,但此前五天没人做,因为每个人都以为别人在管这件事。

markdown 复制代码
## Agent 权限矩阵模板(填 ✅/❌/🔔)

| 操作 | 是否允许 | 是否需要审批 | 每日上限 | 备注 |
|---|---|---|---|---|
| 读取数据 | ✅ | ❌ | 无限制 | 只读操作,无风险 |
| 生成草稿 | ✅ | ❌ | 20 篇/天 | 草稿不发布,不对外可见 |
| 发布内容 | ✅ | ❌ | 5 篇/天 | cooldown 30min |
| 修改已发布内容 | 🔔 | ✅ | 5 次/天 | 需要 Telegram 确认 |
| 删除内容 | ❌ | --- | 0 | 永不允许,只能由人操作 |
| 调用外部 Webhook | 🔔 | ✅ | 10 次/天 | 需记录 payload |
| 修改系统配置 | ❌ | --- | 0 | 高风险,禁止 |
| 发送通知/邮件 | ✅ | ❌ | 50 条/天 | 仅内部通知渠道 |

有几个填表时冒出来的发现让我们意识到之前有多粗心:

Agent 能修改系统配置。我们的 Agent 有调用内部 API 的权限,其中一个接口可以修改推送策略。当时给权限是因为"有时候需要动态调整"------但我们从没想清楚"谁决定什么时候动态调整"。

Agent 能调用外部 Webhook。理由是"有时候需要触发下游任务"。但 Webhook 是最不可逆的操作之一------发出去就发出去了,没有撤回。

填完矩阵之后,"删除"和"修改配置"两列全是 ❌。为什么?因为当你真的坐下来思考"如果 Agent 今晚出 bug 误触发了这个操作,我能接受吗",答案是显然的。没有 Agent 需要以自动方式删除东西。如果它需要,那是流程设计的问题,不是权限的问题。

这张矩阵本身不是代码,是共识。它的价值是让整个团队对"Agent 有什么权力"有一个统一的认知,而不是各自揣测。


运维债 2:失败恢复------"重试 3 次"不是失败处理,是鸵鸟

Agent 的失败分两类,处理方式完全不同,混在一起处理是最常见的错误。

第一类:瞬时失败

网络超时、限流 429、临时服务不可用。这类失败是可重试的,指数退避就够了。

第二类:结构性失败

  • 内容违规被平台拒绝(重试 100 次结果一样)
  • 操作已成功执行但状态未记录(重试会产生副作用)
  • 认证过期(重试会连续产生 401)
  • Agent 的逻辑本身进入了死循环

把这两类都丢给"重试 3 次"的结果,我们第 4 天领教过:一个内容审核失败的任务重试了 5 次,每次都发出了相同的内容,被平台判定为刷量。

我们后来做了一个简单的失败分类器:

python 复制代码
# failure_classifier.py

from enum import Enum

class FailureType(Enum):
    TRANSIENT = "transient"      # 可重试
    STRUCTURAL = "structural"    # 不可重试,需人工介入
    SIDE_EFFECT = "side_effect"  # 操作可能已成功,幂等检查后再决定

def classify_failure(error_code: int, error_msg: str, attempts: int) -> FailureType:
    # 认证/授权失败 → 结构性,别重试
    if error_code in (401, 403):
        return FailureType.STRUCTURAL

    # 内容审核/业务逻辑拒绝 → 结构性
    if error_code in (422, 451) or "content_policy" in error_msg:
        return FailureType.STRUCTURAL

    # 已执行但未确认 → 先查幂等再决定
    if error_code == 0 and "timeout" in error_msg:
        return FailureType.SIDE_EFFECT

    # 限流/服务不可用 → 可重试,读 Retry-After
    if error_code in (429, 503):
        return FailureType.TRANSIENT

    # 超过重试次数 → 升级为结构性
    if attempts >= 3:
        return FailureType.STRUCTURAL

    return FailureType.TRANSIENT

# 在 task runner 里使用
async def handle_failure(task, error_code, error_msg):
    failure_type = classify_failure(error_code, error_msg, task.attempts)

    if failure_type == FailureType.TRANSIENT:
        delay = 2 ** task.attempts * 60  # 2m, 4m, 8m
        await queue.retry_after(task, delay_seconds=delay)

    elif failure_type == FailureType.SIDE_EFFECT:
        # 先检查操作是否已在目标侧生效
        already_done = await check_idempotency(task)
        if already_done:
            await queue.complete(task)  # 标记为完成,不重试
        else:
            await queue.retry_after(task, delay_seconds=60)

    elif failure_type == FailureType.STRUCTURAL:
        await queue.fail_permanently(task)
        await notify_on_call(f"Task {task.id} hit structural failure: {error_msg}")

这段代码不长,但它显式地区分了"应该重试"和"不应该重试"------这个区分本来应该在 Agent 系统设计的第一天就做,而不是出了事故才做。

还有一个比较反直觉的发现:"操作已成功但状态未记录"的失败,比显式错误更危险

显式错误(422 Content Rejected)你能看到。但 Agent 发完请求、服务端已执行、但响应在网络中丢失了------这种情况 Agent 以为操作失败了,会重试,而目标侧已经执行了两次。解法是 Outbox Pattern:先写"我要做 X",执行完再写"X 完成了",两步写到不同存储,进程重启后先查 Outbox。这块在上篇(Agent 跑 24 小时后,我补上的 6 个运维护栏)有完整实现,这里不重复。


运维债 3:人工接管机制------"暂停"不等于"关掉"

这笔债我们欠得最晚,但付出的代价最直接。

第 5 天凌晨,Agent 在执行一个批量任务时行为开始异常------不是报错,而是开始生成质量极差的内容(上下文丢失导致,后来确认是 context window 溢出)。我们想"暂停"它,结果没有暂停入口,只能 kill

kill 掉进程有三个问题:

  1. 丢失 in-progress 任务的状态,重启后可能重跑
  2. 不知道它当前做到哪一步了
  3. 重启后 Agent 从头开始,之前的错误状态(比如错误的 context)还在

我们后来实现了一个轻量的分级接管协议,核心就是 4 个端点:

bash 复制代码
# Level 1 - 软暂停:完成当前任务后不接新任务
curl -X POST http://localhost:8765/control/pause \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"reason": "人工检查", "operator": "ethan"}'

# Level 2 - 检查点停止:跑完当前步骤就停
curl -X POST http://localhost:8765/control/checkpoint-stop

# Level 3 - 状态快照后立即停止
curl -X POST http://localhost:8765/control/freeze \
  -d '{"save_context": true}'

# 恢复(Level 1/2/3 均通用)
curl -X POST http://localhost:8765/control/resume \
  -d '{"operator": "ethan"}'

四个端点,核心是这样一张状态机:

sql 复制代码
RUNNING ──pause──→ PAUSING ──task_done──→ PAUSED
   │                                         │
   ├──freeze──→ FROZEN                   resume│
   │                                         │
   └──terminate──→ TERMINATED         RUNNING ←─┘

这套协议的关键不是技术细节,而是让"暂停"成为 Agent 内置行为 ,而不是外部强杀。Agent 代码里每完成一个步骤都会检查一次控制状态------如果是 PAUSING,下一个任务不取了;如果是 FROZEN,立刻保存状态并退出。

有了这个机制之后,我们遭遇的 2 次异常都在 2 分钟内完成了接管,没有额外的状态损坏。


运维债 4:记忆污染------长跑 Agent 的 context 会越跑越"脏"

这是最难察觉的一笔债。

当 Agent 第一次跑的时候,它的上下文是干净的------只有任务指令和当次的工具返回结果。但随着任务轮次累积,很多团队的 Agent 会把历史任务的摘要追加进 context(为了让它"记住"之前做了什么)。

这件事在短期内看起来很有用------Agent 知道"今天已经发了 3 篇文章",不会重复发。但有一个问题几乎没有人在设计阶段想到:追加进去的历史摘要本身可能是错的

如果某次任务失败了,失败的部分结果也可能被摘要进了 context。下一次任务开始时,Agent 的"起点认知"就带着上次的错误残留。错误会随着轮次传播,直到某次表现异常才被人发现------而此时追溯原因已经很困难了,因为 context 已经被多轮任务的摘要叠加了好几层。

我们叫它记忆污染(Memory Poisoning)。

解法不是不用 context 记忆,而是分清两种信息:

python 复制代码
# memory_strategy.py

# 类型 A:事实性状态(不会过期、不会出错)
# 存数据库,Agent 每次启动时查询,不放进 context
FACTUAL_STATE = {
    "published_articles_today": "SELECT COUNT(*) FROM audit WHERE action='publish' AND date=today()",
    "pending_tasks": "SELECT COUNT(*) FROM task_queue WHERE status='pending'",
    "last_run_at": "SELECT MAX(completed_at) FROM task_queue WHERE status='completed'"
}

# 类型 B:上下文理解(当前任务内有效,跨任务无效)
# 只放进单次任务的 context,任务完成后丢弃
EPHEMERAL_CONTEXT = [
    "当前任务的用户意图",
    "这次调用的中间结果",
    "临时的推理过程"
]

# 错误的做法:把 B 类信息持久化到下次任务
def wrong_approach():
    summary = agent.summarize_last_run()  # 包含了错误的中间状态
    next_context = f"上次运行摘要:{summary}\n\n{new_task}"
    # 错误传播了

# 正确的做法:用数据库存 A 类,B 类每轮清空
def right_approach(new_task):
    # 每次启动时,从数据库查询干净的事实状态
    state = db.query_state(FACTUAL_STATE)
    fresh_context = f"""
当前事实状态(来自数据库,不是上轮摘要):
- 今日已发布:{state['published_articles_today']} 篇
- 待处理任务:{state['pending_tasks']} 个

当前任务:{new_task}
"""
    return fresh_context

这个改动之后,我们的 Agent 在连续运行 48 小时后,性能没有出现之前观察到的"越跑越奇怪"现象。不是因为 Agent 变聪明了,而是因为它每次启动时拿到的是干净的事实,而不是上次的推理残留。


运维债 5:成本归因------不知道钱花在哪,就不知道该砍哪里

我们用 Agent 跑了 7 天之后,算了一下 token 账单:比预期高了 2.3 倍。

高在哪里?这才是问题所在------我们说不清楚。

Agent 每天跑很多任务,每个任务都消耗 token,但我们没有按任务类型聚合的账单。结果是一笔大数字,没法决策:是某类任务本来就贵,还是某个步骤在无效循环?是 context 带得太多,还是工具调用太频繁?

补上成本归因只需要一个中间件层:

python 复制代码
# token_tracker.py

import time
from dataclasses import dataclass, field
from typing import Optional
import sqlite3

@dataclass
class TokenRecord:
    task_id: str
    task_type: str       # 'write_draft', 'publish_check', 'research', etc.
    model: str
    input_tokens: int
    output_tokens: int
    cost_usd: float
    step: str            # 任务内的步骤名
    timestamp: float = field(default_factory=time.time)

# 按任务类型的成本汇总(一周数据)
COST_SUMMARY_QUERY = """
SELECT
    task_type,
    COUNT(*) as task_count,
    SUM(input_tokens) as total_input,
    SUM(output_tokens) as total_output,
    ROUND(SUM(cost_usd), 4) as total_cost_usd,
    ROUND(AVG(cost_usd), 4) as avg_cost_per_task
FROM token_records
WHERE timestamp > strftime('%s', 'now', '-7 days')
GROUP BY task_type
ORDER BY total_cost_usd DESC;
"""

我们跑了这个查询之后,看到的结果:

任务类型 任务次数(7天) 总成本(USD) 单次均价 占比
research_topic 34 $4.21 $0.124 38%
write_draft 19 $3.87 $0.204 35%
publish_check 89 $1.44 $0.016 13%
format_review 56 $0.98 $0.018 9%
context_summary 203 $0.56 $0.003 5%

context_summary 单次很便宜,但运行了 203 次------比所有其他任务加起来都多。往下钻一层才发现:有一段逻辑在每次工具调用前都会重新 summarize 一次全量 context,触发了 203 次 summary 任务。这个无效调用不改代码根本发现不了,因为从日志里看它"正常运行"。

修掉这个之后,下一周账单降了 31%。

这就是成本归因的价值------不是让你削减 Agent 能力,而是让你找到那些功能上多余、费用上昂贵的调用。


5 笔债的优先级和实施顺序

不是所有团队都要同时补这 5 项,按影响和成本排一下:

债务 不补的最坏后果 实施成本 建议优先级
权限矩阵 Agent 误触不可逆操作(删除/外发) 低(2小时填表) P0
失败分类 重试产生副作用,被平台标记异常 中(1天代码) P0
人工接管 异常时只能强杀,状态损坏 中(1天代码) P1
记忆清洗 长跑后 Agent 行为越来越不可预测 中(半天重构) P1
成本归因 账单说不清楚,优化无法决策 低(几小时加日志) P2

权限矩阵是最快能做的------不需要写代码,只需要团队坐下来填一张表,达成共识。但恰恰是这种"不需要代码"的事,在工程师团队里最容易被推迟。

有个观察可能反直觉:权限矩阵的主要价值不是防止 Agent 乱来,而是防止团队里的人对 "Agent 能做什么" 各执一词。出了事之后你会发现,5 个人里可能有 3 种不同的理解------有人以为 Agent 不能改配置,有人以为只要有 API Key 就可以改。矩阵的价值是消除这种信息差。


我们在第 7 天发现的真正问题

7 天的运维经历之后,我觉得把所有问题归结为"技术债"有点过度简化了。

真正的问题是这样的:团队接入 Agent 的速度,比团队建立 "对 Agent 的共同认知" 的速度快了一个数量级。

每个人都知道"我们有个 Agent 在跑",但没有人能清楚说出:

  • 它今天做了什么(→ 需要审计日志摘要)
  • 它有什么权力(→ 需要权限矩阵)
  • 出问题了谁负责(→ 需要明确值班角色)
  • 长期跑下去成本怎么算(→ 需要成本归因)

这些不是工程问题,是团队信息对齐问题。工程机制(代码、协议、日志)是手段,目标是让团队里每个人对 Agent 的边界有一致的认知。


可复制的治理清单

把上面 5 项整理成团队可以直接执行的检查列表:

markdown 复制代码
## AI Agent 治理清单 v1(openclaw-lab 验证)

### 上线前(必须)
- [ ] 权限矩阵填写完成,所有不可逆操作标注 ❌ 或 🔔(需审批)
- [ ] 失败分类逻辑实现:区分 transient / structural / side_effect
- [ ] 人工接管端点存在:至少支持 pause 和 resume
- [ ] 幂等 key 设计:发布/外发类操作必须有去重机制

### 上线后 7 天内(建议)
- [ ] context 记忆策略审查:事实性状态从数据库读,不从上轮摘要读
- [ ] 成本归因上线:按任务类型拆分 token 消耗
- [ ] 审计日志接入:记录所有产生外部副作用的操作

### 持续运营(每周)
- [ ] 有人每天花 5 分钟看 Agent 审计摘要
- [ ] 每周一次成本查询,识别异常涨幅
- [ ] 每两周回顾一次权限矩阵,是否有权限需要收紧

这张清单不完整,也不需要完整------它的目标是让团队在 7 天内建立最基础的可见性,而不是一步到位搭建完整的 Agent 治理平台


常见问题

Q:权限矩阵应该多细粒度?操作级还是接口级?

A:从操作语义级别开始,不要从接口级开始。"发布文章"比"调用 /api/v1/posts POST" 更容易达成共识------前者人人能理解,后者只有写代码的人看得懂。接口级权限是实现细节,矩阵是团队共识文档,两个层面都要有,但不要混在一张表里。

Q:记忆污染多久之后会开始明显影响输出质量?

A:根据我们的观察,context 带有历史摘要的 Agent,在第 5-7 个任务周期之后开始出现可察觉的偏差(比如推断任务状态时更依赖"上次说过的话"而非工具实时返回的结果)。这个拐点因 context 窗口大小、摘要质量和任务类型不同而不同。没有通用数字,但"三天不出问题不代表没问题"------改变通常是渐进的,要主动测,不能等到明显异常才排查。

Q:成本归因是实时的还是离线的?

A:先做离线的。用 SQLite 存每次大模型调用的 token 数和模型名,每天跑一次汇总查询。不需要实时 dashboard------对大多数团队来说,离线日报的信息密度已经够做决策了。等到你识别出需要实时告警的指标(比如某类任务成本突然涨了 50%),再上实时监控。从离线查询开始,工程成本低,验证了价值再投入。

相关推荐
浩风祭月2 小时前
我用 AI 辅助重构了遗留项目的认证模块:从明文存储到 OAuth 2.0 的安全升级
后端·php·ai编程
Java知识技术分享2 小时前
opencode安装ui-ux-pro-max和frontend-ui-ux技能
人工智能·ui·个人开发·ai编程·ux
虎妞05002 小时前
AI 编程助手横评:Cursor vs Copilot vs Claude Code
ai编程·开发工具·cursor·github copilot·claude code
来让爷抱一个2 小时前
MonkeyCode Agent深度解析:AI如何自主完成从编码到部署
开源·ai编程·monkeycode
阿伟AI说3 小时前
Codex 桌面版接入国产模型系列二:Codex++
java·开源软件·ai编程·腾讯云ai代码助手
hoaxxcj3 小时前
AI编程2026:Copilot桌面应用发布,我们正在经历一场不可逆的范式转移
copilot·agent·ai编程·github copilot·编程工具
这个DBA有点耶3 小时前
Vibe Coding 是什么?当“感觉编程”遇上数据库
数据库·人工智能·架构·学习方法·ai编程·程序员创富·改行学it
测试开发技术3 小时前
AI 测试赋能全流程实战 | Agent Skill + AI 赋能「需求分析」
自动化测试·人工智能·自动化·需求分析·ai编程·ai测试
财经资讯数据_灵砚智能3 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年6月12日
人工智能·python·ai·信息可视化·自然语言处理·ai编程·灵砚智能