MINARA

可观测性

运行中 Agent 的日志、审计与调试

当 Agent 出现异常时,必须弄清原因。Minara Agent 会产生三条独立的信息流:结构化日志审计日志层级事件。每条流都有其用途。本页说明各类信息的归属,以及如何排查常见故障。

为什么是三条流,而不是一个大日志? Agent 异常通常呈现三种形态:基础设施故障(LLM 调用失败、数据库锁死)、Agent 执行了错误操作(以错误参数调用工具),或权限逻辑意外拦截了某个操作。把三者混在一条流里会导致难以阅读。分开后,可以直接定位到对应的表:行为问题看 audit,权限问题看 tier_events,基础设施问题看结构化日志。三条流共享 trace_id,需要完整还原时可将它们串联起来。

实际示例6 阶段资金安全会为每次涉及资金的调用向 tier_events 写入一行记录,以便事后证明某笔交易通过了所有预检。

三条信息流

信息流存储位置用途
结构化日志$dataDir/logs/*.ndjson 及标准输出运行状态、启动信息、LLM 调用
审计日志SQLite audit"Agent 调用了哪些工具,结果如何"
层级事件SQLite tier_events"该调用为何被允许或拦截"

经验法则:调试行为问题,先看审计日志;调试基础设施问题,先看结构化日志;调试权限问题,先看层级事件。

结构化日志

apps/agent/src/core/logger.ts 提供了一个简洁的 JSON 日志记录器:

logger.info("skills/registry", "skill_registered", { id });
logger.warn("agent/loop", "max_iterations_reached", { session_id });
logger.error("llm/anthropic-wire", "cache_miss", { reason });

每行是一个 JSON 对象,结构如下:

{
  "ts": "2026-04-14T12:34:56.789Z",
  "level": "info",
  "category": "skills/registry",
  "event": "skill_registered",
  "correlation_id": "turn_abc123",
  "trace_id": "t_xyz",
  "data": { "id": "minara.core" }
}

关联 ID 由包裹轮次执行的 withCorrelation(id, fn) 生成。轮次内产生的所有日志行通过 AsyncLocalStorage 继承相同的 ID。执行 grep correlation_id=turn_abc123 $dataDir/logs/*.ndjson 即可获取某轮次的完整记录。

日志轮转

日志按天轮转,写入 $dataDir/logs/。通过以下环境变量配置级别:

  • LOG_LEVELdebug / info / warn / error,默认 warn

设置 LOG_LEVEL=debug 是安全的:结构化格式可用 jq 过滤噪音,但日志量约为原来的 4 倍。

审计日志:事实来源

所有工具调用均经过 apps/agent/src/core/audit-log-hook.ts 中的 auditLogHook。表结构如下:

CREATE TABLE audit (
  id              TEXT PRIMARY KEY,
  session_id      TEXT,
  trace_id        TEXT,
  tool_name       TEXT,
  tool_set        TEXT,
  args_json       TEXT,       -- redacted
  result_json     TEXT,
  blocked         INTEGER,
  block_reason    TEXT,
  permission_tier INTEGER,
  source          TEXT,       -- user | cron | autopilot | delegation
  duration_ms     INTEGER,
  created_at      INTEGER
);
CREATE VIRTUAL TABLE audit_fts USING fts5(
  tool_name, block_reason, args_json, content='audit'
);

FTS5 支持对完整历史进行全文检索:

SELECT created_at, tool_name, blocked, block_reason
  FROM audit
 WHERE audit MATCH 'withdraw'
 ORDER BY created_at DESC
 LIMIT 50;

排查时从这里开始。 当用户反馈"Agent 行为异常"时,第一步是按顺序拉取该会话的审计记录。LLM 的推理文本、工具参数、原始工具输出和时间戳都在其中。

脱敏处理

tools/_shared/result.ts 中的脱敏器会在数据写入审计日志前,将已知敏感字段(api_keysecretpasswordtokenprivate_keymnemonicseed)替换为 ***。加上密钥从不作为工具参数传入(而是在工厂函数阶段从 process.env 读取)的规则,审计日志可以放心分享给支持团队,或直接附在 bug 报告中,无需额外脱敏。

层级事件:拦截原因

core/permission-tier-hook.ts 对每次权限决策(无论允许还是拦截)都会向 tier_events 写入一行:

CREATE TABLE tier_events (
  id            TEXT PRIMARY KEY,
  tool_name     TEXT,
  source        TEXT,
  tier          INTEGER,
  allow_ceiling INTEGER,
  decision      TEXT,          -- allow | block | pending_confirmation
  reason        TEXT,
  trace_id      TEXT,
  created_at    INTEGER
);

审计日志告诉你发生了什么;层级事件告诉你权限系统为何做出该决定。两张表通过 trace_id 关联,常一起查询:

SELECT a.tool_name, a.blocked, te.decision, te.reason
  FROM audit a
  LEFT JOIN tier_events te ON te.trace_id = a.trace_id
                           AND te.tool_name = a.tool_name
 WHERE a.session_id = ?
 ORDER BY a.created_at;

关联 ID 与追踪 ID

系统中有两种不同的 ID:

  • correlation_id 以轮次为粒度,在 Agent 循环每轮开始时生成,出现在结构化日志和审计记录中。
  • trace_id 以工作流运行或信号为粒度,从 SignalContextWorkflowInstance 传递到下游所有工具调用,包括子 Agent 委托。

用户聊天轮次通常只有新的 correlation_id,没有 trace_id。定时任务触发时两者都有。trace_id 可用于查询"14:03 的 BTC 提醒在整个生命周期内做了什么,包括它派生的子 Agent"。

常见排查场景

"Agent 为何拒绝调用 X?"

SELECT tool_name, decision, reason, created_at
  FROM tier_events
 WHERE tool_name = 'swap'
   AND decision = 'block'
 ORDER BY created_at DESC
 LIMIT 10;

查看 reason 字段,常见值如下:

  • tier_exceeds_ceiling:技能未激活,或当前轮次的 allowRiskTier 低于工具的风险等级。
  • analysis_to_trade_boundary:该轮次已进行了分析调用;交易调用必须在独立的用户消息中发起。
  • daily_cap_exceededdaily_spend 加上本次调用的名义金额将超过 MINARA_DAILY_CAP_USD
  • kill_switch_active:某人(或 Agent 本身)触发了 kill
  • tool_set_not_allowed:当前轮次的 allowedToolSets 不包含该工具。

"为什么这次轮次很慢?"

SELECT tool_name, AVG(duration_ms) AS avg_ms, COUNT(*) AS n
  FROM audit
 WHERE session_id = ?
 GROUP BY tool_name
 ORDER BY avg_ms DESC;

结合按 correlation_id 过滤的结构化日志,可查看 LLM 调用耗时和缓存命中率。

"提示词缓存是否生效?"

在日志中检索 llm.cache_read_input_tokens。如果某会话的该值全为 0,说明可缓存的提示词块不稳定(可能每轮都在重新生成,导致缓存失效)。参见 LLM 集成

"Autopilot 昨夜做了什么?"

SELECT created_at, tool_name, blocked, substr(result_json, 1, 200)
  FROM audit
 WHERE source = 'autopilot'
   AND created_at > ?
 ORDER BY created_at;

健康检查接口

HTTP 网关提供以下接口:

  • GET /healthz:存活探针,进程正常且数据库可打开时返回 200
  • GET /status:就绪状态详情,包含数据库统计、技能数量、活跃触发器、最近一次 LLM 调用成功时间。

完整 schema 参见 API 参考

对接外部工具

如需将日志接入 Loki / Datadog / Grafana Cloud,可通过管道传输标准输出:

docker run minara 2>&1 | vector --config vector.toml

所有标准输出均为合法的 NDJSON,没有单独的"结构化日志"导出路径,标准输出即为规范来源。


可观测性的设计有意保持简单:三张表、一种日志格式、一个关联字段、一个追踪字段。出现问题时,读行记录;一切正常时,忽略它。这就是全部的设计。

本页目录