AI Agent 工具调用权限账本:别只做 RBAC,把每一次调用都变成可回放证据

如果你已经把 AI Agent 接到工单、知识库、代码仓库、CRM 或内部运维系统,迟早会遇到一个尴尬问题:

Agent 的这一次工具调用,到底为什么被允许?

很多团队第一反应是做 RBAC:用户有 admin 角色,所以 Agent 可以调用 update_ticket;用户只有 viewer,所以只能调用 search_docs。这当然必要,但它远远不够。RBAC 只能解释"理论上能不能调用",解释不了生产事故里真正需要的东西:这一次调用发生时,Agent 看到了什么上下文?拿的是哪一个授权快照?策略版本是多少?有没有人工审批?输入输出有没有被改写?失败后补偿动作是什么?三天后还能不能回放?

这篇文章想讲一个更工程化的做法:给 Agent 的每一次工具调用建立"权限账本"(permission ledger)。它不是普通日志,也不是把所有请求体粗暴塞进 Elasticsearch。它是一条可审计、可回放、可关联 trace、可用于事后追责和策略迭代的调用证据。

先说结论:

  • RBAC 是准入表,权限账本是证据链。 前者告诉你"规则怎么写",后者告诉你"这次为什么发生"。
  • 不要只记录 tool name 和 status。 至少要记录授权快照、策略版本、风险等级、输入输出摘要、审批证据、trace/span id 和补偿状态。
  • 账本不是明文 dump。 生产系统里应使用字段级脱敏、摘要哈希、保留周期和不可篡改写入,避免把审计系统变成新的数据泄露源。
  • MCP、OAuth、OpenTelemetry 不是替代品。 它们分别解决协议授权、令牌流转、可观测性结构;权限账本负责把"授权决策"和"工具副作用"串成业务可理解的证据。

1. 一个很常见的事故:权限是合法的,调用是不该发生的

假设你做了一个客服 Agent。它可以读取用户资料、查询订单、更新工单状态。某天用户说:

"把这个客户的退款工单标成已处理,并顺手把备注改成我说的这段话。"

Agent 调用了:

json 复制代码
{
  "tool": "ticket.update",
  "args": {
    "ticket_id": "T-92817",
    "status": "resolved",
    "note": "客户已同意退款完成"
  }
}

调用成功。第二天业务同学发现:客户并没有同意,Agent 是从一段旧聊天记录里误读出来的。

你去查权限系统,RBAC 显示:客服主管确实有 ticket:update 权限;Agent 是代表客服主管执行;工具调用也没有越权。看起来一切"合法"。但安全、合规和业务负责人真正想问的是:

  1. 这一次调用时,Agent 基于哪些上下文判断"客户已同意"?
  2. 这个写操作是否属于高风险动作?
  3. 当时策略版本是否要求人工审批?为什么没有触发?
  4. Agent 使用的授权是即时授权、长期令牌,还是继承用户会话?
  5. 工具输入是否经过二次校验?输出是否产生了可恢复的副作用?
  6. 如果要复盘,能否只重放策略判定,而不重新执行写操作?

如果系统只能回答"用户有权限,所以允许",那就是审计断层。

这就是权限账本要解决的问题。

2. RBAC 为什么不够:Agent 的权限不是一次性判断

传统后台系统里,权限判断通常发生在 API 边界:请求进来,检查用户、角色、资源、动作,允许或拒绝。这个模型默认"调用者知道自己在做什么"。

Agent 系统不一样。Agent 的一次工具调用中间多了几层不确定性:

  • 用户意图可能含糊;
  • 模型可能误读上下文;
  • 检索结果可能过期;
  • 工具描述可能被错误理解;
  • 多步任务中,第 5 步的决策依赖第 1 步的中间结果;
  • 工具结果会反过来进入上下文,影响下一次调用;
  • 写操作可能产生外部副作用,比如发消息、改状态、扣额度、创建订单。

所以 Agent 的权限判断至少包含三层:

层级 传统问题 Agent 场景的新问题
身份层 谁在调用? Agent 是代表谁调用?用户本人、系统账号还是租户机器人?
策略层 这个角色能否执行动作? 当前上下文、风险等级、数据范围、时间窗口是否允许?
证据层 API 是否返回成功? 这次决策依据是什么?能否复盘、回放、追责、补偿?

RBAC 主要覆盖前两层的一部分。权限账本覆盖第三层,并把前两层的关键快照记录下来。

注意这里的"快照"很重要。生产事故经常不是当下规则错,而是当时规则是什么已经查不清了。今天你修了策略,昨天的调用用的是旧策略;今天用户角色被降级,昨天调用时还是管理员;今天工具 manifest 里标成高风险,昨天还是中风险。如果没有账本,你只能靠猜。

3. 权限账本应该记录什么

我建议把每一次 tool_call 拆成 8 类字段。

3.1 调用身份:who on behalf of whom

至少记录:

json 复制代码
{
  "tenant_id": "acme",
  "actor_type": "agent",
  "agent_id": "support-agent-v3",
  "on_behalf_of": "user_123",
  "session_id": "sess_789"
}

关键不是"Agent 叫什么",而是 Agent 代表谁行动。读操作可以允许系统身份执行,写操作最好绑定真实用户或明确的服务账号。否则出了问题,会变成"机器人干的",没有责任边界。

3.2 工具定义:what was callable

记录工具 manifest 的版本和摘要:

json 复制代码
{
  "tool": "ticket.update",
  "tool_version": "2026-07-20.3",
  "tool_manifest_hash": "sha256:...",
  "risk_level": "high",
  "side_effect": "write_external_state"
}

不要只记 ticket.update。工具描述、参数 schema、风险等级都会变。manifest hash 可以让你三个月后确认:当时 Agent 看到的工具定义到底是哪一版。

3.3 授权快照:grant snapshot

记录当时授权状态,而不是只记录最终 allow/deny:

json 复制代码
{
  "grant": {
    "scope": ["ticket:read", "ticket:update"],
    "resource_filter": { "region": "cn-east", "team": "refund" },
    "expires_at": "2026-07-27T09:00:00+08:00",
    "grant_source": "user_delegated_oauth"
  }
}

MCP 的 HTTP 授权规范里,受保护 MCP Server 可以作为 resource server,Client 代表资源所有者请求受保护资源,授权链路会涉及资源元数据、授权服务器发现和访问令牌。工程上这很好,但落到事故复盘时,仅知道"有 token"还不够,你要知道 token 当时代表的 scope、资源边界和过期时间。

3.4 策略判定:why allowed

记录策略引擎给出的结构化解释:

json 复制代码
{
  "policy": {
    "policy_version": "perm-policy-42",
    "decision": "approval_required",
    "matched_rules": ["write_requires_approval", "refund_team_only"],
    "reason": "high-risk write requires human approval"
  }
}

很多团队的策略判断只有一个 boolean。boolean 对机器够用,对审计不够用。你至少要知道命中了哪些规则,哪个规则把结果从 allow 改成 approval_required。

3.5 输入摘要:what was requested

不要把完整 prompt、客户资料、聊天记录塞进审计日志。建议记录:

  • schema 校验后的参数;
  • 高敏字段的脱敏值;
  • 原始输入 hash;
  • 关键上下文引用 id;
  • 模型生成的调用理由摘要。

例如:

json 复制代码
{
  "input": {
    "args_redacted": { "ticket_id": "T-92817", "status": "resolved", "note": "[REDACTED:78chars]" },
    "args_hash": "sha256:...",
    "context_refs": ["msg_173", "kb_42"],
    "agent_rationale": "用户要求关闭退款工单;Agent 判断客户已同意"
  }
}

这里最容易犯错的是"为了审计,全部保存"。这会让审计系统变成最大的敏感数据仓库。权限账本要保存足够复盘的信息,而不是保存所有明文。

3.6 输出与副作用:what changed

写操作必须记录副作用摘要:

json 复制代码
{
  "output": {
    "status": "success",
    "external_id": "T-92817",
    "changed_fields": ["status", "note"],
    "before_hash": "sha256:...",
    "after_hash": "sha256:...",
    "compensation": {
      "available": true,
      "tool": "ticket.rollback",
      "deadline": "2026-07-30T00:00:00+08:00"
    }
  }
}

如果是不可逆动作,比如发出通知、提交付款、删除资源,就应该在策略层升级为 approval_requireddeny,而不是事后才说"日志里有记录"。

3.7 Trace 关联:how to find it in observability

把权限账本和 trace/span 关联起来:

json 复制代码
{
  "trace": {
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "span_id": "00f067aa0ba902b7",
    "parent_span_id": "..."
  }
}

OpenTelemetry 的 Trace API 把一次 operation 建模为 span。Agent 系统里可以把 agent.runpolicy.checktool.callapproval.waittool.compensate 都作为 span 或 event。这样你在可观测性平台里看到延迟、错误、重试时,可以跳到权限账本;在审计账本里看到一次高风险 allow 时,也可以跳回完整 trace。

3.8 完整性:can it be tampered with

最后是完整性字段:

json 复制代码
{
  "ledger": {
    "entry_id": "led_01j...",
    "prev_hash": "sha256:previous",
    "entry_hash": "sha256:current",
    "written_at": "2026-07-27T08:10:00+08:00"
  }
}

最低配可以用 append-only JSONL + hash chain。更严格的场景可以写入不可变对象存储、审计数据库或外部合规系统。重点是:不要让业务服务可以随便改旧账。

4. 一个最小可行架构

可以从 5 个组件开始:

text 复制代码
User / Agent Session
        |
        v
Tool Call Planner
        |
        v
Permission Gateway
  |        |        |
  |        |        +--> Tool Manifest Registry
  |        +-----------> Grant Snapshot Provider
  +--------------------> Policy Engine
        |
        v
Tool Executor -----> External System
        |
        v
Permission Ledger Writer -----> append-only ledger
        |
        v
Replay Verifier / Audit UI

几个工程边界要明确:

  1. Agent 不直接调工具。 它只能提交 tool intent,由 Permission Gateway 代理执行。
  2. 策略判断发生在执行前。 不要先执行再补日志。
  3. 账本写入要覆盖 denied 调用。 被拒绝的调用同样重要,尤其能发现 prompt 注入和越权尝试。
  4. 高风险写操作先写 pending,再等审批。 审批通过后追加 approved/executed 记录,不要覆盖原记录。
  5. 回放只回放判定,不默认重放副作用。 Replay Verifier 应该默认 dry-run。

5. 可运行 Demo:一个 150 行以内的权限账本

下面是一个简化版 Node.js demo。它没有依赖外部服务,直接写 JSONL。你可以复制到 ledger-demo.mjs 运行。

js 复制代码
import { appendFileSync, readFileSync, existsSync } from 'node:fs';
import crypto from 'node:crypto';

const LEDGER = './permission-ledger.jsonl';

const tools = {
  'ticket.search': {
    version: '2026-07-27.1',
    risk: 'low',
    sideEffect: 'read',
    scopes: ['ticket:read']
  },
  'ticket.update': {
    version: '2026-07-27.1',
    risk: 'high',
    sideEffect: 'write_external_state',
    scopes: ['ticket:update']
  },
  'payment.refund': {
    version: '2026-07-27.1',
    risk: 'critical',
    sideEffect: 'money_movement',
    scopes: ['payment:refund']
  }
};

function sha256(x) {
  return crypto.createHash('sha256').update(typeof x === 'string' ? x : JSON.stringify(x)).digest('hex');
}

function lastHash() {
  if (!existsSync(LEDGER)) return 'GENESIS';
  const lines = readFileSync(LEDGER, 'utf8').trim().split('\n').filter(Boolean);
  if (!lines.length) return 'GENESIS';
  return JSON.parse(lines.at(-1)).ledger.entry_hash;
}

function redactArgs(args) {
  const out = {};
  for (const [k, v] of Object.entries(args)) {
    if (/note|comment|message|content|phone|email/i.test(k)) {
      out[k] = `[REDACTED:${String(v).length}chars]`;
    } else {
      out[k] = v;
    }
  }
  return out;
}

function decide({ tool, grant, hasApproval }) {
  const manifest = tools[tool];
  if (!manifest) return { decision: 'deny', rules: ['unknown_tool'], reason: 'tool is not registered' };

  const missing = manifest.scopes.filter(s => !grant.scopes.includes(s));
  if (missing.length) {
    return { decision: 'deny', rules: ['missing_scope'], reason: `missing scope: ${missing.join(',')}` };
  }

  if (manifest.risk === 'critical') {
    return { decision: 'deny', rules: ['critical_action_blocked'], reason: 'critical money movement is disabled for agents' };
  }

  if (manifest.risk === 'high' && !hasApproval) {
    return { decision: 'approval_required', rules: ['high_risk_requires_approval'], reason: 'high-risk write requires human approval' };
  }

  return { decision: 'allow', rules: ['scope_match', 'risk_accepted'], reason: 'scope and risk policy passed' };
}

function fakeExecute(tool, args) {
  if (tool === 'ticket.search') return { status: 'success', rows: 3, outputHash: sha256({ rows: 3 }) };
  if (tool === 'ticket.update') {
    return {
      status: 'success',
      externalId: args.ticket_id,
      changedFields: Object.keys(args),
      beforeHash: sha256('before-state'),
      afterHash: sha256(args),
      compensation: { available: true, tool: 'ticket.rollback' }
    };
  }
  return { status: 'skipped' };
}

function callTool({ tenant, agentId, userId, sessionId, tool, args, grant, hasApproval = false }) {
  const manifest = tools[tool];
  const traceId = crypto.randomBytes(16).toString('hex');
  const spanId = crypto.randomBytes(8).toString('hex');
  const policy = decide({ tool, grant, hasApproval });

  const base = {
    identity: { tenant, actor_type: 'agent', agent_id: agentId, on_behalf_of: userId, session_id: sessionId },
    tool: manifest ? {
      name: tool,
      version: manifest.version,
      manifest_hash: sha256(manifest),
      risk_level: manifest.risk,
      side_effect: manifest.sideEffect
    } : { name: tool },
    grant_snapshot: {
      scopes: grant.scopes,
      resource_filter: grant.resourceFilter || {},
      expires_at: grant.expiresAt,
      grant_source: grant.source || 'user_delegated'
    },
    policy: { version: 'perm-policy-42', ...policy },
    input: {
      args_redacted: redactArgs(args),
      args_hash: sha256(args),
      context_refs: ['msg:last-user', 'retrieval:last-3'],
      agent_rationale: 'tool selected by planner; gateway performs final decision'
    },
    trace: { trace_id: traceId, span_id: spanId }
  };

  let output = { status: 'not_executed' };
  if (policy.decision === 'allow') output = fakeExecute(tool, args);

  const prev = lastHash();
  const entryWithoutHash = { ...base, output, ledger: { prev_hash: prev, written_at: new Date().toISOString() } };
  const entryHash = sha256(entryWithoutHash);
  const entry = { ...entryWithoutHash, ledger: { ...entryWithoutHash.ledger, entry_hash: entryHash } };
  appendFileSync(LEDGER, JSON.stringify(entry) + '\n');
  return entry;
}

function verifyLedger() {
  if (!existsSync(LEDGER)) return true;
  let prev = 'GENESIS';
  for (const line of readFileSync(LEDGER, 'utf8').trim().split('\n').filter(Boolean)) {
    const entry = JSON.parse(line);
    const expectedPrev = entry.ledger.prev_hash;
    const actualHash = entry.ledger.entry_hash;
    const clone = structuredClone(entry);
    delete clone.ledger.entry_hash;
    if (expectedPrev !== prev) return false;
    if (sha256(clone) !== actualHash) return false;
    prev = actualHash;
  }
  return true;
}

const grant = {
  scopes: ['ticket:read', 'ticket:update'],
  resourceFilter: { team: 'refund' },
  expiresAt: '2026-07-27T09:00:00+08:00',
  source: 'oauth_user_delegated'
};

console.log('read:', callTool({ tenant: 'acme', agentId: 'support-v3', userId: 'u1', sessionId: 's1', tool: 'ticket.search', args: { q: 'refund' }, grant }).policy.decision);
console.log('write no approval:', callTool({ tenant: 'acme', agentId: 'support-v3', userId: 'u1', sessionId: 's1', tool: 'ticket.update', args: { ticket_id: 'T-92817', status: 'resolved', note: '客户已同意退款完成' }, grant }).policy.decision);
console.log('write approved:', callTool({ tenant: 'acme', agentId: 'support-v3', userId: 'u1', sessionId: 's1', tool: 'ticket.update', args: { ticket_id: 'T-92817', status: 'resolved', note: '客户已同意退款完成' }, grant, hasApproval: true }).policy.decision);
console.log('refund:', callTool({ tenant: 'acme', agentId: 'support-v3', userId: 'u1', sessionId: 's1', tool: 'payment.refund', args: { order_id: 'O-1', amount: 199 }, grant: { ...grant, scopes: [...grant.scopes, 'payment:refund'] }, hasApproval: true }).policy.decision);
console.log('ledger ok:', verifyLedger());

运行结果大概是:

text 复制代码
read: allow
write no approval: approval_required
write approved: allow
refund: deny
ledger ok: true

这个 demo 很小,但已经覆盖了生产系统里最关键的 4 个分支:低风险读取直接允许;高风险写入没有审批则进入审批;审批后执行并记录补偿信息;资金类关键动作即使有 scope 也被策略拒绝。

6. 四个实验结果:账本比日志多解决了什么

我用上面的 demo 跑了 4 组场景,观察点不是性能,而是事故复盘能力。

场景 普通日志能看到 权限账本能额外回答
低风险读取 tool=search, status=success 当时 scope、manifest hash、trace id、输入 hash
高风险写入未审批 status=not_executed 命中 high_risk_requires_approval,为什么没有执行
高风险写入已审批 status=success 审批状态、changed fields、before/after hash、补偿工具
关键资金动作 status=deny 即使 scope 存在,策略仍按 critical_action_blocked 拒绝

最有价值的是第二和第四个场景。很多事故不是"系统没拦住",而是"系统拦住了但没人知道为什么";或者"用户有权限,但这类动作不应该交给 Agent 自动执行"。权限账本把这些边界变成可查询的数据。

如果你把账本接入审计 UI,可以做出很实用的查询:

sql 复制代码
-- 最近 24 小时,所有高风险 allow
SELECT * FROM permission_ledger
WHERE tool_risk_level IN ('high', 'critical')
  AND policy_decision = 'allow'
  AND written_at > now() - interval '24 hours';

-- 某个策略版本上线后,approval_required 是否异常升高
SELECT policy_version, policy_decision, count(*)
FROM permission_ledger
WHERE written_at > '2026-07-27'
GROUP BY policy_version, policy_decision;

-- 某个用户被 Agent 代理执行过哪些写操作
SELECT tool_name, changed_fields, trace_id, written_at
FROM permission_ledger
WHERE on_behalf_of = 'user_123'
  AND side_effect != 'read';

这类查询比"翻日志找关键字"稳定得多。

7. 权限账本和 MCP/OAuth/OpenTelemetry 的关系

容易混淆的点是:既然 MCP 有授权规范,OAuth 有 scope,OpenTelemetry 有 trace,那为什么还要权限账本?

我的理解是:

  • MCP 授权解决"客户端如何代表资源所有者访问受保护 MCP Server"。它是协议层能力。
  • OAuth/scope解决"令牌是否拥有某些资源权限"。它是授权凭据能力。
  • OpenTelemetry解决"运行时调用链如何观测"。它是可观测性结构。
  • 权限账本解决"这一次工具调用的授权、策略、上下文、输入输出、副作用、审批和补偿如何形成证据"。它是审计与治理能力。

它们应该组合,而不是互相替代。

一个比较好的落地方式是:

  1. MCP Client 通过授权流程拿到用户委托令牌;
  2. Agent 计划调用工具,但不直接执行;
  3. Permission Gateway 读取 tool manifest 和 grant snapshot;
  4. Policy Engine 给出 allow/deny/approval_required;
  5. Tool Executor 执行或拒绝;
  6. Ledger Writer 写入账本;
  7. Trace span 记录延迟、错误、重试和 ledger entry id。

这样,协议、权限、执行和审计就闭环了。

8. 生产落地的 9 条建议

8.1 不要让 Agent 直接持有长期高权限 token

Agent 最好拿短期、可撤销、绑定用户或任务的授权。长期 admin token 一旦进入 Agent runtime,后面所有策略都会变得脆弱。即使你有账本,也只是记录了事故如何发生。

8.2 把工具按副作用分级

至少分四类:

  • read:读取,不改变外部状态;
  • write_internal:改内部草稿、缓存、临时状态;
  • write_external:改业务系统状态;
  • irreversible:不可逆或高成本动作,比如付款、删除、外发通知。

不同等级对应不同默认策略。不要只靠 tool name 猜风险。

8.3 denied 也要入账

被拒绝的调用是安全信号。比如某次 prompt 注入诱导 Agent 调 user.export_all,策略拒绝了。如果你不记录 denied,就错过了攻击样本和策略改进依据。

8.4 账本字段要可索引,不要只存 blob

policy_decisionrisk_leveltool_nameon_behalf_oftrace_idapproval_id 应该是独立字段。否则审计查询会退化成全文搜索。

8.5 明文 payload 要克制

输入输出建议分三层:

数据 建议
低敏参数 可明文记录
中高敏字段 脱敏后记录 + 原文 hash
极高敏内容 只记录引用 id、分类、hash,不进账本

不要因为"审计需要"就把用户隐私复制一份。

8.6 策略版本必须可回放

每条账本记录策略版本和 tool manifest hash。策略仓库要保留历史版本。回放时用当时版本判断一次,看看今天的策略会不会给出不同结果。这个能力对策略迭代很有用。

8.7 审批是状态机,不是一个 boolean

高风险调用常见状态:

text 复制代码
planned -> approval_required -> approved -> executed -> compensated
                         \-> rejected
                         \-> expired

每个状态都应追加账本事件,而不是覆盖同一行。覆盖会破坏证据链。

8.8 和 trace 打通

账本适合回答"为什么允许";trace 适合回答"在哪里慢、哪里错、重试了几次"。两边通过 trace_idspan_idledger_entry_id 互跳,排障效率会高很多。

8.9 先从高风险工具做起

不用一开始覆盖所有工具。优先接入:

  • 外发消息;
  • 修改业务状态;
  • 资金/额度相关操作;
  • 删除/归档;
  • 读取高敏数据;
  • 跨租户或跨团队数据访问。

从这些工具开始,收益最大。

9. 常见反对意见

"我们已经有 API access log 了,还需要吗?"

需要。API log 通常记录 HTTP 层事实:谁请求了哪个接口、状态码多少、耗时多少。权限账本记录 Agent 语义层事实:为什么选择这个工具、当时策略如何判定、授权快照是什么、审批证据在哪里、输出副作用如何补偿。两者不是一个层级。

"这会不会太重?"

如果你给每个 read tool 都写完整账本,确实重。更实际的做法是分级:低风险读取记录轻量字段,高风险写入记录完整证据,不可逆动作要求审批和补偿计划。账本也可以异步写,但执行前的策略判定不能异步。

"会不会拖慢 Agent?"

策略判断和账本写入会增加延迟,但通常比模型调用小得多。真正需要注意的是外部审计存储不可用时怎么办。我的建议是:

  • 对高风险写操作,账本写入失败则拒绝执行;
  • 对低风险读取,账本写入失败可以降级为本地缓冲;
  • 对不可逆动作,审计系统不可用时直接熔断。

"能不能只靠 prompt 约束 Agent 不要乱调工具?"

不能。Prompt 是行为建议,不是权限边界。工具权限必须在模型外部的 deterministic gateway 中执行。Agent 可以提出 intent,但最终 allow/deny 应由可测试、可审计的策略引擎决定。

10. 最后:生产 Agent 的安全感来自证据,不来自信任

Agent 从 demo 到生产,最大的变化不是多接几个工具,而是它开始产生真实副作用。它会改状态、发通知、创建记录、触发流程。到了这个阶段,"我们相信模型会正确调用工具"是不够的。

你需要的是一条能回答这些问题的证据链:

  • 谁让 Agent 做的?
  • Agent 代表谁做的?
  • 当时有哪些权限?
  • 策略为什么允许或拒绝?
  • 工具输入输出是什么摘要?
  • 产生了什么副作用?
  • 有没有审批?
  • 能不能回放?
  • 出错后能不能补偿?

这就是权限账本的价值。

我的建议很简单:如果你的 Agent 还只读知识库,可以先用普通 trace 和 access log;如果它已经开始写业务系统、发消息、改工单、触发财务或运维动作,就不要只做 RBAC 了。给每一次工具调用记一笔账。未来真出事故时,你会感谢今天多写的这几行证据。


参考资料:

  • MCP Authorization specification(2025-06-18):HTTP transport 授权、OAuth 2.1 子集、资源服务器元数据发现。
  • MCP runtime build/buy discussion(2026):agent authorization、OAuth token rotation、audit logging、policy enforcement 是生产 runtime 的关键边界。
  • Production-grade Agent framework documentation:durability、restartability、observability、governance、human-in-the-loop 是生产 Agent 的基础能力。
  • OpenTelemetry Trace API:Tracer、Span 与 operation tracing 为 tool call 关联账本提供通用结构。
相关推荐
跨境Jacky2 天前
亚马逊CLI(跨境电商语境下命令行数据工具区别于AWSCLI
跨境电商·mcp·sorftime
奋飛3 天前
AI应用工程:Agent 的能力是如何扩展的?——Tool、Skill、Workflow 与 MCP 的职责边界
agent·workflow·mcp·skills·ai应用工程
辞忧九千七3 天前
MCP 协议完全指南:从原理到 LangGraph 集成,打造即插即用的 AI Agent 工具生态
agent·langgraph·mcp
csdn_aspnet3 天前
如何将 Cursor MCP 与 VS Code 连接
vscode·cursor·mcp·composio
吴佳浩4 天前
MCP:从原理、源码、实战到企业落地,一篇彻底讲透 AI 世界的标准协议
人工智能·agent·mcp
玉&心4 天前
关于配置mcp服务端sse-message-endpoint和sse-endpoint的注意点
spring·springai·mcp
MicrosoftReactor5 天前
技术速递|智能体构建智能体:基于 Microsoft Agent Framework 与 Foundry 的 Skill 优先架构蓝图
ai·架构·agent·智能体·mcp
牧艺6 天前
从 Tool Calling 到 MCP Server:把业务能力做成 Agent 可复用接口
agent·全栈·mcp