可觀測性
運行中 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_LEVEL(debug/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_key、secret、password、token、private_key、mnemonic、seed)替換為 ***。加上密鑰從不作為工具參數傳入(而是在工廠函數階段從 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以工作流運行或信號為粒度,從SignalContext或WorkflowInstance傳遞到下游所有工具調用,包括子 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_exceeded:daily_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,沒有單獨的"結構化日誌"導出路徑,標準輸出即為規範來源。
可觀測性的設計有意保持簡單:三張表、一種日誌格式、一個關聯字段、一個追蹤字段。出現問題時,讀行記錄;一切正常時,忽略它。這就是全部的設計。