Ongrid
ongridio/ongrid · 源码级 基于 cmd/ + internal/ 逐文件阅读

逐文件还原 Ongrid 的运维大脑

一个 Ops AI + 根因分析(RCA)+ 远程执行平台——Coordinator + Specialist 多 Agent 架构、零入站端口反向隧道、自带 Prometheus/Loki/Tempo 可观测性栈、Slack/Telegram/飞书双向 IM 与浏览器 SSH。所有结论来自源码文本,含真实函数名与算法。

# ongridio/ongrid · Go (Chi + cloudwego/eino + singchia/geminio) + TypeScript/React · Apache-2.0

3 BC
iam · manager · edge
7 persona
Specialist + reviewer
6 provider
LLM 热路由
0 入站口
边缘往外拨号
向下探索
01
architecture · three bounded contexts

分层架构与三 BC

一句话定位:ongrid 把"运维 AI"拆成三个互不 import 的 Bounded Context——iam(用户/鉴权)、manager(控制面 + AIOps 大脑)、edgeagent(每台主机上的只读探针 + 执行器)。

三者通过 .go-arch-lint.yml 强制隔离,只能借 internal/pkg/**(业务无关共享层)和 api/ 通信。技术栈:Go(Chi router + cloudwego/eino agent 框架 + singchia/geminio 反向隧道)+ TypeScript/React SPA + MySQL/SQLite + frontier broker + Docker Compose。

设计哲学:深度数据查询和高危执行能力都下沉到 edge 和 specialist,coordinator 只做"派给谁 / 直接答 / 直接拒"的调度,并通过 cmdpolicy 沙箱 + ReviewGate 二审把"AI 误操作"的爆炸半径压到最小。边缘 agent 永远是发起方(dial-out);manager 想调用 edge 的能力是通过 frontier broker 反向调用边缘已注册的 RPC handler,而非主动连边缘端口。

系统全景 · manager / edge / broker
                            浏览器 SPA (web/  React+TS)              IM 平台
                          ┌──────────────────────┐      Slack / Telegram / 飞书(Lark)
                          │ /chat  /webshell      │      DingTalk / WeCom / Webhook
                          │ /topology /incidents  │              │
                          └──────────┬───────────┘              │ (webhook + 长连接 stream)
                                     │ HTTP / WebSocket / SSE     │
┌────────────────────────────────────┼─────────────────────────────┼──────────────────────────┐
│  cmd/ongrid  (manager + iam 装配)    │                             │                          │
│                                     ▼                             ▼                          │
│  ┌──────────────────────────────────────────────┐   ┌────────────────────────────────────┐ │
│  │ internal/manager/server (Chi handlers)        │   │ internal/manager/biz/imbridge       │ │
│  │  aiops/http  webshell/http  edgeauth ...       │   │  bridge → adapter → AgentSession    │ │
│  └───────────────────────┬───────────────────────┘   │  provider/{feishu,slack,telegram}   │ │
│                          ▼                            │  StreamSupervisor (长连接 reconcile)│ │
│  ┌─────────────────────────────────────────────────┐ └──────────────────┬─────────────────┘ │
│  │ chatruntime.Runtime  (eino ReAct 图引擎)         │◄───────────────────┘                   │
│  │  ┌──── COORDINATOR (persona="default") ────────┐ │                                        │
│  │  │ 受限 toolBag: query_devices / query_incidents│ │                                        │
│  │  │   get_topology / query_knowledge / search_web│ │     ┌──────────────────────────────┐  │
│  │  │   read_source/grep_source + AgentTool        │ │     │  internal/pkg/llm (router)    │  │
│  │  └──────────────┬───────────────────────────────┘ │────►│  OpenAI/Anthropic/GLM/        │  │
│  │     AgentTool.SpawnWorker (同步派活)             │ │     │  DeepSeek/Gemini/Kimi 热路由  │  │
│  │                 ▼                                 │ │     └──────────────────────────────┘  │
│  │  ┌──── SPECIALIST WORKERS (各自 persona) ───────┐│ │                                        │
│  │  │ specialist-compute / -disk / -network / -ops ││ │     ReviewGate 二审装饰器              │
│  │  │ specialist-sre / incident-investigator       ││ │     (write/destructive → spawn reviewer)│
│  │  │ 每个 worker = filterToolsForAgent 后的子 bag ││ │                                        │
│  │  └──────────────┬───────────────────────────────┘│ │                                        │
│  └─────────────────┼─────────────────────────────────┘ │                                        │
│         tools/ (BaseTool + 装饰器链)                    │                                        │
│  query_promql/logql/traceql  correlate_incident         │     数据存储                           │
│  expand_topology  host_bash  host_restart_service ...   │  MySQL/SQLite  topology graph          │
└─────────────────┬───────────────────────────────────────┴────────────────────────────────────┘
                  │ tunnel.Caller.Call(edgeID, method, body)   ▲ AcceptStream (webshell forward)
                  ▼                                            │
        ┌──────────────────────────────────────────────────────────────┐
        │  frontier broker (singchia/frontier:1.2.5)  —— 路由/鉴权中枢   │
        └──────────────────────────────┬───────────────────────────────┘
                                        ▲  edge 主动 Dial 出站 (TCP/TLS),零入站端口
                                        │  geminio RetryEnd 自动重连
        ┌───────────────────────────────┴───────────────────────────────┐
        │  cmd/ongrid-edge (装在被管主机上)                               │
        │  tunnel.Client  →  register_edge / heartbeat / push_*          │
        │  RPC handlers:  get_host_load / get_process_list / execute_skill│
        │  cmdpolicy 沙箱(只读 binary 白名单)  webshell TCP 转发到 :22    │
        │  plugins: hostmetrics / metrics / logs / traces / procmetrics  │
        └───────────────────────────────────────────────────────────────┘
                 Prometheus  ◄── remote_write ──  Loki ◄── push ──  Tempo
iam

用户 / 鉴权

独立 Bounded Context,负责身份与权限。其余 BC 不能直接 import 它,只能走 api/ 与 internal/pkg/** 共享层。

manager

控制面 + 大脑

装配 Chi handlers、IM bridge、chatruntime.Runtime(eino ReAct 图)。AIOps 全在 manager 进程内。

edgeagent

探针 + 执行器

装在每台主机上的只读探针 + 受限执行器,dial-out 连 broker,注册 RPC handler 供 manager 反向调用。

反向调用而非反向代理:manager 调边缘能力走 tunnel.Caller.Call(edgeID, method, body),经 broker 路由到该 edge 已注册的 handler——主机上不开 22 / 80 / 443 任何入站口。
02
modules · the source map

核心模块拆解

系统由两个二进制(cmd/ongrid 云端主进程 + cmd/ongrid-edge 边缘 agent)和五大业务域组成。下表是逐文件阅读后梳理出的模块职责映射。

表 02 · 模块 / 路径 → 职责
模块 / 路径作用
cmd/ongrid/main.go云端主进程:装配 iam + manager,buildAIOpsRuntime 把 LLM router / toolBag / SkillRegistry / AgentRegistry 拼成 chatruntime.Runtime,注册 "default" coordinator persona
cmd/ongrid-edge/main.go边缘 agent:tunnel.NewClient 拨号,注册 collector / host_files / restart_service / bash / webshell handler,启动 plugins supervisor
aiops/chatruntime/runtime.go协调员主循环 Handle:归属校验 → @-mention 内联 → 持久化 user 消息 → 解析 skill/persona → 组 system prompt → BuildReActGraph → Invoke → 翻译 Reply
aiops/chatruntime/worker.goSpecialist 子 agent 生命周期(SpawnWorker / SendToWorker / StopWorker),filterToolsForAgent 按 persona 白/黑名单裁剪 toolBag,KB-first 预热
aiops/graph/react.goBuildReActGraph:外层 MessageAssembler → ReActSubgraph → OutputProjector,内层复用 eino 的 react.Agent;每轮注入 <system-reminder> 反漂移块
aiops/tools/ + decorators/30+ BaseTool(promql/logql/traceql/correlate_incident/expand_topology/host_*),AgentTool 派活工具;装饰器链 tenant_bind → review_gate → timeout → audit → ratelimit → metric
aiops/investigator/investigator.go告警触发时的主动诊断(后台 worker pool,直接调 correlate_incident + 单轮 LLM)
edgeagent/cmdpolicy/host_bash 只读 binary 白名单(ClassReadFS/ReadSystem/Mixed/Network/Denied),write 子命令拒绝;sandbox 加 path/network allowlist
edgeagent/webshell/handler.go把边缘变成纯 TCP 转发器:manager 开 stream → edge dial 127.0.0.1:22 → 双向 io.Copy
internal/pkg/tunnel/基于 geminio 的反向隧道:client.go 指数退避 + RetryEnd 自动重连 + 路由失效检测重连;messages.go 手写 JSON 消息体
manager/biz/imbridge/IM 双向桥:bridge.go inbound 归一化 → session 映射 → 渐进编辑;stream_supervisor.go 长连接 reconcile;provider/{feishu,slack,telegram}
2 binaries

主进程 + 边缘

cmd/ongrid(manager+iam)与 cmd/ongrid-edge(探针+执行器)。

eino ReAct

AIOps 内核

新内核在 graph/,与旧 agent.go 并存,本文聚焦 graph 内核。

30+ tools

装饰器链

每个 BaseTool 过同一条装饰器链,横切关注点统一注入。

geminio

反向隧道

internal/pkg/tunnel 把 manager↔edge 双向 RPC 收口到一处。

03
multi-agent · coordinator + specialist

Coordinator + Specialist 多 Agent 协作

模型:Coordinator 和 Specialist 是同一个 chatruntime.Runtime 实例里的不同 persona,区别只在 toolBag 和 system prompt。Coordinator = sess.AgentID 为空/"default";其余 agents/*.md 里定义的都是 worker。

buildAIOpsRuntime(main.go:2376)是整个 AIOps 装配的咽喉——它把每个依赖恰好穿过一次。Coordinator 的 coordinatorToolNames(main.go:2362)被刻意收窄成只读定位 + 知识工具,注释直言这是 "rule 1 的物理执行":coordinator 的 toolBag 里根本没有 query_promql / host_bash 这类深度工具,所以 LLM 即使想 inline 查也调不到,只能走 AgentTool 派给 specialist。

cmd/ongrid/main.go — coordinator 受限 toolBag
var coordinatorToolNames = []string{
    "query_devices", "query_incidents", "get_topology", "query_knowledge",
    "search_web", "list_repo_sources", "read_source", "grep_source",
}

派活路径:coordinator 的 LLM 调 AgentTool(PascalCase,刻意对齐 claude-code 的工具名以利模型选择,见 agent_tool.go:91)。AgentTool 是 coordinator-only 工具,tools 包不能 import chatruntime(会循环),所以它通过 WorkerSpawner 窄接口调用,由 main.go 装配处把 *chatruntime.Runtime shim 成该接口。AgentTool 默认同步派活,拿到 worker 结论后强制 coordinator 收口:

aiops/tools/agent_tool.go — 同步派活 + 强制收口
w, err := t.spawner.SpawnWorker(ctx, SpawnWorkerRequest{
    AgentName: args.SubagentType,
    Prompt:    args.Prompt,
    Locale:    basetool.LocaleFromContext(ctx),  // 把 UI 语言透传给 specialist
})
// ...
res.Hint = "Specialist " + args.SubagentType + " 已经返回最终结论(见 result 字段)。" +
    "直接基于它给用户写最终答复;**不要再调用任何工具**…"
图 03 · 一次派活的同步往返
Coordinator
persona=default
AgentTool
SpawnWorker
同步阻塞
filter
specialist-X
子 toolBag
graph.Invoke
result
最终结论
收口
用户答复
不再调工具

Worker 隔离(worker.go:522 runWorker):每个 worker 跑独立 graph.Invoke,toolBag 经 filterToolsForAgent 用 persona 的白名单 + 黑名单裁剪(black wins),并强制剥离 coordinatorOnlyTools(AgentTool/SendMessage/TaskStop)——worker 不能 spawn 嵌套 worker。worker 有自己的 session id(worker-<8hex>),消息历史对父不可见。

aiops/chatruntime/worker.go — toolBag 角色过滤
var coordinatorOnlyTools = map[string]bool{
    "AgentTool": true, "SendMessage": true, "TaskStop": true,
}
func filterToolsForAgentRole(bag, agentDef, isCoordinator, viewerOnly) []BaseTool {
    // viewer 降级:先砍掉所有 Class!=read 的工具
    // coordinator-only 工具只在 isCoordinator=true 时保留
    // 否则按 persona 白名单过滤,再减黑名单(支持 *_skill 通配)
}

Agent loop(graph/react.go):外层包一个 eino react.Agent,关键修正是 MaxStep = MaxIterations*2 + 2——因为 eino 把每个节点 visit 都算一步,一轮 ReAct = ChatModel + ToolsNode = 2 步,不 ×2 会让 LLM 在 30 轮上限时只跑到 ~15 轮就 ErrExceededMaxSteps。反漂移机制:每轮在 user 消息前注入独立的 <system-reminder> user-role 消息(graph/react.go:271 buildSystemReminder),内容 = 硬编码基线规则 + persona 的 critical_reminder + runtime 动态启发式(runtime.go:863 calcDynamicHints:同工具连续失败、迭代数过高、重复调用同一 (tool,args)、"承诺但没发 tool_call" 检测)。

失败模式 · 弱模型幻觉工具名:GLM-4 等幻觉出不在 schema 的工具名 → eino "tool not found in toolsNode" 直接 abort 整个图。对策:coordinator 叠加 CoordinatorStubs(redirect_stub.go)占住 host_bash / get_host_load 等高幻觉名,返回"请用 AgentTool 派给 specialist-X"而非崩溃(runtime.go:519)。
失败模式 · 重复派同一 specialist:E2E eval D1 观测到 122 次 tool call / 240s → AgentTool 用 sha256(subagent_type+prompt) 做 90s dedupe(agent_tool.go:137),命中时返回"重复派活拦截" hint。
04
tunnel · zero inbound ports

零入站端口的反向隧道 / Browser SSH

源码:internal/pkg/tunnel/client.go + edgeagent/webshell/handler.go。边缘 agent 用 gclient.NewRetryEndWithDialer 主动连 frontier broker——主机上不开任何入站口。

dial-out:首次拨号指数退避 1s→2s→…封顶 60s;连上后断线由 geminio RetryEnd 透明重连。手柄 blob Meta{AccessKey, SecretKey} 在 connect 时塞进 geminio 不透明 Meta,server 端解码后做 AuthFunc。manager 想调边缘能力时是反向 Call:tunnel.Caller.Call(edgeID, method, body) 经 broker 路由到该 edge 已注册的 handler。一个微妙坑——manager 重启会清掉 broker 路由表但 edge 的 TCP 还活着,所以 client 额外检测 broker 路由失效错误重连:

internal/pkg/tunnel/client.go — 路由失效检测重连
func shouldReconnect(err error) bool {
    msg := err.Error()
    return strings.Contains(msg, "not found") ||
        strings.Contains(msg, "mismatch clientID")
}

Browser SSH:架构经过一次重构——SSH 协议栈完全搬到 manager 侧,边缘退化成"一屏 TCP 转发器"。manager 用一个携带 {"target":"127.0.0.1:22"} Meta 的 stream 进 edge,edge 校验 target 在 allowlist 内后 dial 本地 sshd,双向 io.Copy:

edgeagent/webshell/handler.go — target allowlist
var allowedTargets = map[string]bool{
    "127.0.0.1:22": true, "localhost:22": true,
}
// localhost narrow scope 是安全边界——否则被攻破的 manager 能把 edge 当跳板打内网

manager 侧 biz/webshell/router.go 维护 SessionID → Sink 目录,把 edge 推回的 stdout 路由到对应浏览器 WebSocket;记录 per-session 字节数、支持 admin kill、idle watchdog、per-user/per-device 并发限制。SSH 口令 SSHPass 是一次性的,Dial 后从 edge 内存抹掉,永不落盘/落日志(messages.go:84 注释)。

关键设计:让 manager 独占 webshell 的 policy/audit/并发/踢人逻辑,edge 越薄越安全(无 SSH client、无 pty、无 session map)。
05
rca · correlate then trace to patient zero

根因 RCA:拓扑 + 信号关联

RCA 由两条路组成:被动(用户在 /chat 问)和主动(investigator 告警触发)。两者都吃同一个核心工具 correlate_incident——一次性把告警周围的四类信号打成一个 bundle,让 LLM 不用 chain 十次工具。

aiops/tools/correlate_incident.go — 信号 bundle
type correlateIncidentBundle struct {
    Incident    incidentSummary   // 规则名/severity/target/fired_at/labels
    Window      windowRange       // 以 first_fired_at 为中心 ±window/2
    MetricPanel []metricSeries    // PromQL range,top-3 series by magnitude
    LogPanel    []logEntry        // Loki: {edge_id} |~ "(?i)error|panic|oom|fatal|fail"
    TracePanel  []traceEntry      // Tempo: service.name 过滤
    Edge        *edgeSnapshot     // 当前 cpu/mem/up + 24h flap 计数
    Skipped     map[string]string // 哪个面板因何跳过(无 client/无 edge_id…)
    Truncated   map[string]int    // 100KB 上限时砍 logs→traces→metric values
}

PromQL 表达式由 metricExprFor 从规则的闭集 metric 名(cpu_pct/mem_pct/disk_used_pct/load1…)翻译,再用 wrapPerEdge 用 ... and on(edge_id) (group by (edge_id) ({edge_id="N"})) 限定到单台。每个子查询有独立短 deadline(trace/log/edge 互不阻塞),整体 60s 封顶,bundle 超 100KB 时按"先砍 logs 再砍 traces 最后砍 metric values"降级,并在 Truncated 里告知 LLM "你看的不是全貌"。

expand_topology

拓扑关联 BFS

在业务 topology graph 上做 BFS,默认只走"传播失败"的边(PropagatesFailure 的 relation:depends_on/deployed_on/routes_to),direction 支持 downstream(爆炸半径)/ upstream(谁导致我挂)/both。一次拉全 relation 进内存(假设 ≤10k/tenant),每个命中带 hop 数 + relation 类型 + semantics tag。

incident-investigator

因果回溯到 0 号病人

persona 把 RCA 定义成"输出一条因果链 根因→…→告警症状"而非现象摘要,强调"找最早偏离的,不是最显眼的",用 query_change_events(产品侧变更常是 0 号病人)、expand_topology 往上游走、query_traceql 找慢 span 发起方逐层上溯。

investigator.go

主动诊断 pool

告警 first fire 时后台 spawn——3 worker + 100 buffer 的 pool,直接调 correlate_incident 拿 bundle,单轮 LLM 出 ai_initial_diagnosis Event 写回,SPA 在 incident 详情顶部高亮。完全 additive,LLM 没配/失败都静默退出,不阻塞告警 ingestion。

失败模式 · 近似匹配:incident 行只存 rule_key 不存完整 ConditionsJSON,所以 promExprForIncident 用启发式(rule_key 命中闭集名 / labels["metric"] hint / annotations hint)近似,匹配不上则 metric panel 标 skipped。
06
im bridge · bidirectional messaging

IM 双向集成:飞书 / Telegram / Slack

源码:manager/biz/imbridge/bridge.go。provider 各自的 webhook/stream handler 先验签 + 解密,归一化成 InboundMessage,bridge 保持平台无关。

inbound 流程(HandleInbound):去重(provider:app_id:event_id,防长轮询 replay 重复应答)→ 解析 app/thread 映射(按 (im_app_id, im_chat_id, thread_id) 一聊一 session,群里所有人共享)→ /new 等命令重置 session → 发 "✦ 思考中…" placeholder → 跑 agent + 节流编辑器渐进编辑 IM 消息。outbound 渐进编辑:newStreamEditor 把 agent SSE 的 assistant chunk 节流后不断 edit 同一条 IM 消息。

飞书验签/解密(provider/feishu/verify.go)有个反直觉点——飞书 v2 events 用的是 SHA-256 拼接而非 HMAC:

imbridge/provider/feishu/verify.go — SHA-256 拼接验签
// sig = sha256(timestamp + nonce + encryptKey + body), hex
func VerifyEventSignature(timestamp, nonce, encryptKey string, body []byte, signatureHex string) error {
    h := sha256.New()
    h.Write([]byte(timestamp)); h.Write([]byte(nonce))
    h.Write([]byte(encryptKey)); h.Write(body)
    // ...
}
// DecryptEvent: AES-256-CBC,key=sha256(encrypt_key),IV=密文前16字节,PKCS#7 去填充

长连接管理(stream_supervisor.go):每个启用的 stream-mode IM app 一个 goroutine,30s reconcile DB——新增 app 拉起 client、禁用/密钥轮换则 cancel 重建。runClientWithRestart 指数退避封顶 60s。locale:每个 channel 可 pin locale,bridge 在 user content 后追加语言指令(localeDirective),coordinator 再透传给 specialist,避免 GLM 默认中文答英文问题。

失败模式/待确认:当前所有 IM 会话归属一个固定 service account user(serviceUserID),per-IM-user 绑定(S3)未落地(adapter.go:31)。README 提到 DingTalk/WeCom,但 provider/ 目录只看到 feishu/slack/telegram 三个实现。
07
specialists · capability boundaries

Specialist 能力边界 与 mutating 纪律

能力边界由 agents/*.md frontmatter 的 tools 白名单 + permission_mode + max_turns + worker.go 的 filterToolsForAgent 共同物理约束。所有 specialist 都 permission_mode: read-only、max_turns: 15、第 0 步强制 query_knowledge。

表 07 · 七个 persona 的领域与边界
Persona领域关键工具(白名单)边界
specialist-computeCPU/内存/load/进程/OOM/NUMA/sysctlget_host_load, get_host_processes, rank_edges, find_outlier_edges, query_promql, host_bash不碰磁盘IO/网络/服务启停
specialist-disk文件系统/磁盘容量/inode/大文件host_find_large_files, host_du_summary, host_stat_file, host_bash不碰网络/进程/业务日志,不删改文件
specialist-networkOVS/netfilter/netns/conntrack/bpftool/路由host_bash, host_probe_{http,dns,tcp}, host_netns_inspect, query_promql只读,不碰文件系统/磁盘
specialist-ops服务状态/启停重启/journalctl/cron/部署host_bash, get_host_processes, host_restart_service, query_promql/logql唯一带 mutating 工具者;重启走 ReviewGate 二审
specialist-sre集群健康/趋势/告警优先级/SLO/错误预算correlate_incident, get_active_incidents, find_outlier_edges, rank_edges, query_promql不做单机操作,给优先级 P0-P3
incident-investigator单 incident 端到端因果回溯correlate_incident, query_{change_events,traceql,logql,promql}, expand_topologymax_turns=40,disallowed_tools 禁 execute_skill/host_restart_service
reviewermutating 提案 SOP 二审get_incident_detail, get_edge_summary, get_sop_textbackground=true 异步,model 钉死 claude-opus,disallowed_tools: ["*_skill"]

没有专门的 "DB specialist"——README 说 "SRE / network / DB sub-agents",但 agents/ 里实际是 compute/disk/network/ops/sre/investigator/reviewer 七个,DB 诊断目前落在 specialist-ops(service 状态)+ host_bash(mysql/redis 进程)。

mutating 纪律(cmdpolicy + ReviewGate 双闸):

  • ① edge 侧 cmdpolicy(policy.go):host_bash 的 binary 分五类。systemctl restart 等 write 子命令直接拒(specialist-ops.md 明说"不要用 host_bash 跑 systemctl restart 绕开二审——那条命令在 cmdpolicy 里被拒")。ip netns exec 因为是"递归 shell 可绕过 cmdpolicy"被显式拒。shells/脚本语言/rm/dd/chmod 全在 ClassDenied。Mixed 类默认拒。
  • ② manager 侧 ReviewGate(decorators/review_gate.go):任何 Class=write|destructive 的工具调用前,自动 spawn reviewer worker 审 proposal,只有解析到 Decision: approve 才放行内层工具;reject/解析失败/spawn 失败一律默认拒(safe default)。spawner 为 nil 时也是 "always reject",防止误配置。
aiops/tools/decorators/review_gate.go — mutating 三态
func isMutatingClass(class string) bool {
    switch strings.ToLower(strings.TrimSpace(class)) {
    case "write", "destructive": return true
    default: return false
    }
}
// 三态:approve→跑内层+MarkExecuted;reject→ErrReviewRejected;解析不出 Decision→ErrReviewUndecided(按reject)
ReviewGate 刻意放在 timeout 外(reviewer 自己是 LLM graph,有独立 60s budget)和 audit 外(reject 没 execution 可审,写 chat_mutating_proposals 而非 execution 行)。整条装饰器链是 tenant_bind → REVIEW_GATE → timeout → audit → ratelimit → metric。
08
failure modes · the guardrails

关键技术与失败模式总览

把前面各节出现的边界、触发条件与对策汇成一张表——这是 ongrid 对抗"弱模型 + 高危执行 + 分布式断连"的护栏全景。

表 08 · 场景 → 触发 → 处理
场景 / 边界触发条件处理方式
LLM 幻觉工具名coordinator LLM 调 schema 外的 host_bash 等CoordinatorStubs 占名返回"用 AgentTool 派活";apology 兜底
协调员死循环弱模型 30 轮 / 122 tool calls 空转persona MaxTurns=10 收口 + 每轮 dynamicHints 注入"该收口了" + AgentTool 90s dedupe
重复派同一 specialist同 (subagent_type, prompt) <90ssha256 dedupe 命中,返回"重复派活拦截" hint
user 消息丢失graph 调用中途崩溃LLM 调用前先持久化 user 消息(coordinator + worker 都遵守)
manager 重启路由失效broker route 表清空但 edge TCP 仍活shouldReconnect 检测 "not found"/"mismatch clientID" → 异步 redial + re-register
webshell 被当跳板被攻破的 manager 伪造 target Metaedge allowedTargets 只允许 127.0.0.1:22 / localhost:22
host_bash 越权LLM 想跑 systemctl restart / rm / ip netns execcmdpolicy write 子命令 + ClassDenied + 递归 shell 全拒
mutating 误操作任何 write/destructive 工具ReviewGate spawn reviewer,default-reject;reviewer 三门控(SOP/无并行/有回滚)
viewer 越权viewer 角色聊天toolBag 先砍所有 Class!=read 工具,连 AgentTool 都看不到
correlate bundle 过大bundle > 100KB先砍 logs(>10)→traces(>5)→metric values,Truncated 告知 LLM
IM 长轮询重复应答Telegram getUpdates reconnect replaydedupSet 按 provider:app_id:event_id 去重
worker session 泄漏SpawnWorker 后 session 行 closed_at=NULLgoroutine defer CloseSession(曾累积 161 行 orphan)
子 agent 答错语言GLM persona 中文答英文问locale 经 ctx 透传 coordinator→specialist + 每轮 reminder 重申
告警风暴打爆 LLMrule storm 大量 incidentinvestigator 3 worker + 100 buffer,超出直接丢弃并 warn,不阻塞 ingestion
09
significance · why it matters

生态意义与技术亮点

"调度优于全能"的多 Agent 范式:coordinator 不是更强的 specialist,而是更克制的——它的 toolBag 被物理收窄到只能"派/答/拒",深度能力强制下沉到 specialist。

这把"一个大 prompt 干所有事"的脆弱性换成了"小而专的子 agent + 明确边界",并用 dedupe/MaxTurns/dynamicHints 一整套护栏对抗弱模型的死循环——这是把 SOTA agent 套路(claude-code 的 AgentTool/system-reminder)落到生产 SRE 场景的实践样本。

🛡 zero-trust

零信任执行面

双层闸(edge cmdpolicy 只读白名单 + manager ReviewGate 二审)让"AI 远程跑命令"从不可接受变成可审计、可回滚、爆炸半径可控。reviewer 用最强模型、default-reject、三门控。

🔌 zero-inbound

零入站端口 + Browser SSH

边缘只出站拨号,主机不开任何入站口,却仍能被远程巡检/执行/开 shell。SSH 栈收在 manager 侧让审计/并发/踢人单点可控。

🔬 rca-as-tool

RCA 工程化

correlate_incident 把 metric+log+trace+edge+变更打成单个带降级策略的 bundle,配合拓扑 BFS 和"回溯到 0 号病人"的 persona,告警诞生瞬间就产出带证据的因果链。

📦 self-hosted

自托管 + BYO-model + 多 IM

docker compose up 起全栈(含 Prometheus/Loki/Tempo/Grafana/frontier/searxng),模型层热路由 6 家 provider,IM 层 Slack/Telegram/飞书双向,避免厂商锁定。

一句话总结:ongrid 把"运维 AI"从"贴一堆 Grafana 截图让人猜"升级成"系统在告警诞生瞬间就主动产出一条带证据的因果链",并用双层闸把"AI 远程执行"压到工程可接受的爆炸半径——它降低了团队把"运维 AI"接进现有栈的门槛。
10
open questions · to be confirmed

附:待确认项

本次拆解基于 cmd/、internal/manager/biz/aiops、internal/edgeagent、internal/pkg/tunnel 的逐文件阅读;以下几项仍待与 README / 仓库其余部分对齐。

  • Stars 数:未在仓库内体现,待确认(页面展示为「未知 / —」)。
  • DB specialist:README 提及 "DB sub-agents",但 agents/ 仅含 compute/disk/network/ops/sre/investigator/reviewer,独立 DB persona 待确认(当前 DB 诊断落在 specialist-ops + host_bash)。
  • DingTalk / WeCom:README 列为支持渠道,但 imbridge/provider/ 仅实现 feishu/slack/telegram,钉钉/企微实现待确认。
  • web/ SPA 组件实现:本次只读了 web/src/api/*.ts 层,具体 React 组件(聊天/拓扑/webshell 渲染)实现细节待确认。
  • 旧 agent.go 内核:与 graph 内核并存,本文聚焦 graph 内核(runtime.go/worker.go),旧内核细节未深读。
Ongrid · 源码即文档
ongridio/ongrid · Go + TypeScript · Apache-2.0
基于 cmd/ + internal/ 源码逐文件阅读 · 含实际函数名与算法
github.com/ongridio/ongrid ↗