diff --git a/README.md b/README.md index 5840ab1..6be6e96 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ an auditable trail: PII is redacted before anything reaches a model, output is validated against a strict JSON Schema, and each session archives a content-addressed artifact plus an append-only trace. +**Contents** · [A quick look](#a-quick-look) · [Why this project](#why-this-project) · [Highlights](#highlights) · [Quick start](#quick-start) · [Design notes](#design-notes) · [Architecture](#architecture) · [Documentation](#documentation) · [Safety](#safety) · [License](#license) + +## A quick look + +The web UI — dark-first, sidebar-navigated, every answer cited back to the KB: + +![OpsPilot web UI](docs/assets/webui.png) + +The terminal UI — a REPL with slash commands over the same backend: + +![OpsPilot TUI tour](docs/assets/tui.gif) + ## Why this project AI is reshaping the IT-support industry. OpsPilot is a working answer to a @@ -44,120 +56,69 @@ practical work-assistance layer for IT support look like?** - **Multi-provider** — Anthropic Claude, OpenAI, OpenRouter, Gemini, xAI Grok, or local Ollama; playbooks declare a primary model plus selectable - alternates (down to a local Gemma), switchable per-run from the UI or set - as a team default in the admin module, with automatic fallback when a - provider errors. Admins can curate the selectable list itself — remove or - upgrade models — from the admin module, editing the playbook in place. - Embeddings default to OpenAI, with local Ollama a one-variable switch away - for teams that keep them in-house. Playbooks can - route by complexity, sending the easy majority to a cheap tier and - escalating only what needs it -- **Work-item intake** — pull tickets straight from Jira Service Management - on a JQL scope and post the AI suggestion back as a comment on the ticket: - polling-only (no public endpoint), comment-only (no field is ever touched), - restart-safe state, and a `--replay` mode that demos the whole loop - offline; intake can run on a cheaper model than interactive use, and - remote deployments can push instead via `POST /api/intake` + alternates (down to a local Gemma), switchable per-run or set as a team + default, with automatic fallback when a provider errors and complexity + routing that reserves the expensive tier for what needs it. Admins curate + the alternate list — remove or upgrade models — from the admin module, + editing the playbook in place; embeddings default to OpenAI, one variable + away from local Ollama +- **Work-item intake** — polls Jira Service Management on a JQL scope and + posts the suggestion back as a ticket comment: polling-only (no public + endpoint), comment-only (no field is ever touched), restart-safe, with an + offline `--replay` demo; intake can run on a cheaper model, and remote + deployments push via `POST /api/intake` instead - **KB retrieval with citations** — hybrid vector (LanceDB) + full-text (SQLite FTS5) search fused with RRF; `tool` mode (ReAct) for strong models, `prefetch` injection for weak local ones -- **Memory** — the standing facts about your environment that have no table of - their own: *"never restart the ESXi cluster on a Tuesday evening, finance runs - its month-end batch"*. OpsPilot's second owned domain. An entry is **admitted, - never harvested** — a person writes the sentence and the reason, because an - extractor cannot tell a mid-investigation hunch from a conclusion, and a wrong - entry never raises an error, it just quietly steers the assistant. Entries - carry up to two anchors (an Asset, a site) so a constraint about one site - cannot answer a question about another; they are superseded by appending, so - *"we recorded it wrong"* stays distinguishable from *"the world changed"*; and - a stale review date changes the label an entry carries, never whether it - applies. Memory reaches an answer on its own path rather than through hybrid - search — which is what lets the assistant notice when a recorded constraint - and an ingested document contradict each other, and open a **Conflict** for a - human to settle +- **Memory** — the standing facts about your environment that have no table + of their own: *"never restart the ESXi cluster on a Tuesday evening"*. + Written by a person with a reason, carrying up to two anchors (an Asset, + a site), superseded by appending — and cross-checked against the KB, + opening a **Conflict** when the two disagree ([details](#memory)) - **Consultation** — the surface where an operator actually works a problem, - grounded in the KB, Memory and Skills. Visible to its author and to admins - only, and swept after 90 days, because that is what makes it cheap enough to - think out loud in. Any sentence the assistant says can be **pinned into - Memory** with a reason, in the moment it is said. A **Working set** carries - what you are currently chasing across a chain of conversations — and the - address it lives at, which is what lets anchored Memory reach an answer at - all. It closes by hand, with an unconditional inactivity fallback that - announces itself, because nobody returns to press "close" at the moment a - problem is solved. To *act* on what a conversation found, it escalates into a - Session, carrying a work-item description and nothing else + grounded in the KB, Memory and Skills; any sentence the assistant says can + be pinned into Memory in the moment, and a **Working set** carries the + chase across conversations ([details](#consultation)) - **Asset inventory** — procurement-to-retirement tracking for the devices - your team manages, and the one domain OpsPilot *owns* rather than mirrors: - small teams have no CMDB, so CSV import/export is the migration path in and - out. Eight free-set statuses (no state machine — real inventories are full - of corrections), an append-only event log whose actor comes from the - authenticated caller, and a fulfillment playbook that drafts Assets straight - from a Service Request + your team manages, with CSV as the migration path in and out, free-set + statuses, and an append-only event log ([details](#asset-inventory)) - **Runtime Skills** — reusable `SKILL.md` packages the assistant loads on - demand: it sees a compact catalog of triggers and pulls in the full - procedure when a problem matches, with retrieval-injection fallback for - models too weak to call tools. Admins can have one drafted from a problem - description, or distilled from a **closed Working set** — a problem opened, - worked across several conversations, and finished. The draft keeps the dead - ends, because knowing what to rule out and in what order is the useful half of - a procedure, and it leaves the stopping condition and the tools list **blank - on purpose**: a run that went well never exercised either, and a plausible - guess gets skimmed and merged where a blank cannot. Nothing is admitted by - arriving — moving a draft into `agent_skills/` is a commit, and that commit is - the admission + demand from a compact trigger catalog; drafted from a problem description + or distilled from a closed Working set, and admitted only by a commit + ([details](#runtime-skills)) - **Redaction first** — PII stripped before any content reaches a model or the KB - **Auditable sessions** — content-addressed artifacts, append-only traces, schema-validated output, browsable history. Who acted is taken from the authenticated caller, never from what the caller claims -- **Proposed actions** — a session may put forward a read-only diagnostic with - its dry-run preview and the approval gate's verdict, and **it runs only when a - person presses execute**; request, preview, verdict, actor and outcome all - append to the session's trace. The first batch is diagnostics and contains no - mutation at all — that constraint lives in the artifact schema, where the - intent is a constant, so a mutating action cannot be expressed. Widening it - later is a visible, reviewable diff. Execution happens in hardened Docker (L2) - or gVisor (L3, fail-closed) containers; the approval gate flags risky patterns - but is a defence-in-depth signal, not the boundary — the sandbox is +- **Proposed actions** — a session may put forward a read-only diagnostic + with its dry-run preview; it runs only when a person presses execute, + inside hardened Docker (L2) or gVisor (L3) sandboxes + ([details](#proposed-actions)) - **Compounding wiki** — session insights distilled into lint-checked, lifecycle-managed wiki pages on top of the long-term KB -- **Knowledge bundles** — export the KB, Skills, wiki pages and Memory as one - archive and restore them elsewhere. Per-domain native formats, not a uniform - envelope: Skills and wiki pages stay files, because a Skill is admitted through - a pull request and a pull request has to read as a diff. No vectors travel — - they are bound to an embedding model, so the receiver re-ingests. Sessions and - Consultations deliberately have **no** export: an append-only ledger stops - being one the moment it becomes a file anyone can edit +- **Knowledge bundles** — export the KB, Skills, wiki pages and Memory as + one archive and restore them elsewhere; per-domain native formats, and no + vectors travel ([details](#knowledge-bundles)) - **MCP client** — tools from any Model Context Protocol server (stdio/HTTP) injected into the ReAct loop, with per-server allow/denylists - **Interfaces & channels** — CLI, REPL terminal UI (Textual, slash commands), tabbed web UI (Svelte 5) with KB-augmented chat, FastAPI - backend; a Telegram channel brings the KB chat into your messenger and - files work items with `/intake`; WeCom connects both ways — a group robot - pushes intake suggestions (notify), and a self-built app answers KB - questions in chat (assist) -- **Multi-user & SSO** — login-gated web UI with three roles - (viewer / operator / admin); authenticate against local accounts, - LDAP / Active Directory, or OIDC SSO, with group→role mapping and an - admin module for users, roles, provider status, and audit. Machine - callers (channels, intake) use a Service token; secrets stay in the - environment, never the database. Ships as an all-in-one Docker image — - one `docker run` is a complete, login-gated workbench + backend; a Telegram channel brings KB chat and `/intake` to your + messenger, and WeCom connects both ways — a group robot pushes intake + suggestions (notify), and a self-built app answers KB questions in chat + (assist) +- **Multi-user & SSO** — three roles (viewer / operator / admin) against + local accounts, LDAP/AD, or OIDC SSO with group→role mapping, plus an + admin module for users, roles, provider status, and audit; machine callers + use a Service token, secrets stay in the environment — never the + database — and an all-in-one Docker image makes one `docker run` a + complete login-gated workbench - **Observability** — Prometheus `/metrics`, OTel-compatible JSON logs, `/health` - **Rust hot paths** — chunker (~10×) and tokenizer (~45×) compiled via PyO3/maturin, with a transparent Python fallback; CI enforces ≥5× -## A quick look - -The web UI — dark-first, sidebar-navigated, every answer cited back to the KB: - -![OpsPilot web UI](docs/assets/webui.png) - -The terminal UI — a REPL with slash commands over the same backend: - -![OpsPilot TUI tour](docs/assets/tui.gif) - ## Quick start ### Prerequisites @@ -264,6 +225,88 @@ The container needs no Ollama: `ANTHROPIC_API_KEY` answers chat and For a multi-service deployment (nginx TLS termination, JSM intake, optional Ollama), see [Docker Compose](docs/deployment.md#docker-compose). +## Design notes + +The one-line highlights above compress a lot of deliberate design. This +section keeps the full reasoning for the domains OpsPilot owns and the +decisions that are easy to get wrong. + +### Memory + +The standing facts about your environment that have no table of their own: +*"never restart the ESXi cluster on a Tuesday evening, finance runs its +month-end batch"*. OpsPilot's second owned domain. An entry is **admitted, +never harvested** — a person writes the sentence and the reason, because an +extractor cannot tell a mid-investigation hunch from a conclusion, and a wrong +entry never raises an error, it just quietly steers the assistant. Entries +carry up to two anchors (an Asset, a site) so a constraint about one site +cannot answer a question about another; they are superseded by appending, so +*"we recorded it wrong"* stays distinguishable from *"the world changed"*; and +a stale review date changes the label an entry carries, never whether it +applies. Memory reaches an answer on its own path rather than through hybrid +search — which is what lets the assistant notice when a recorded constraint +and an ingested document contradict each other, and open a **Conflict** for a +human to settle. + +### Consultation + +The surface where an operator actually works a problem, grounded in the KB, +Memory and Skills. Visible to its author and to admins only, and swept after +90 days, because that is what makes it cheap enough to think out loud in. Any +sentence the assistant says can be **pinned into Memory** with a reason, in +the moment it is said. A **Working set** carries what you are currently +chasing across a chain of conversations — and the address it lives at, which +is what lets anchored Memory reach an answer at all. It closes by hand, with +an unconditional inactivity fallback that announces itself, because nobody +returns to press "close" at the moment a problem is solved. To *act* on what +a conversation found, it escalates into a Session, carrying a work-item +description and nothing else. + +### Asset inventory + +Procurement-to-retirement tracking for the devices your team manages, and the +first domain OpsPilot came to *own* rather than mirror: small teams have no CMDB, so +CSV import/export is the migration path in and out. Eight free-set statuses +(no state machine — real inventories are full of corrections), an append-only +event log whose actor comes from the authenticated caller, and a fulfillment +playbook that drafts Assets straight from a Service Request. + +### Runtime Skills + +Reusable `SKILL.md` packages the assistant loads on demand: it sees a compact +catalog of triggers and pulls in the full procedure when a problem matches, +with retrieval-injection fallback for models too weak to call tools. Admins +can have one drafted from a problem description, or distilled from a **closed +Working set** — a problem opened, worked across several conversations, and +finished. The draft keeps the dead ends, because knowing what to rule out and +in what order is the useful half of a procedure, and it leaves the stopping +condition and the tools list **blank on purpose**: a run that went well never +exercised either, and a plausible guess gets skimmed and merged where a blank +cannot. Nothing is admitted by arriving — moving a draft into `agent_skills/` +is a commit, and that commit is the admission. + +### Proposed actions + +A session may put forward a read-only diagnostic with its dry-run preview and +the approval gate's verdict, and **it runs only when a person presses +execute**; request, preview, verdict, actor and outcome all append to the +session's trace. The first batch is diagnostics and contains no mutation at +all — that constraint lives in the artifact schema, where the intent is a +constant, so a mutating action cannot be expressed. Widening it later is a +visible, reviewable diff. Execution happens in hardened Docker (L2) or gVisor +(L3, fail-closed) containers; the approval gate flags risky patterns but is a +defence-in-depth signal, not the boundary — the sandbox is. + +### Knowledge bundles + +Export the KB, Skills, wiki pages and Memory as one archive and restore them +elsewhere. Per-domain native formats, not a uniform envelope: Skills and wiki +pages stay files, because a Skill is admitted through a pull request and a +pull request has to read as a diff. No vectors travel — they are bound to an +embedding model, so the receiver re-ingests. Sessions and Consultations +deliberately have **no** export: an append-only ledger stops being one the +moment it becomes a file anyone can edit. + ## Architecture ![OpsPilot system architecture](docs/assets/architecture.png) diff --git a/README.zh-CN.md b/README.zh-CN.md index 9129ac9..023f3b0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -23,6 +23,18 @@ OpsPilot 通过 playbook 驱动的 AI 管线,把原始 IT 工作项(Work ite 运行都留下可审计的痕迹:内容到达模型之前先做 PII 脱敏,输出经过严格 JSON Schema 校验,每个会话归档一份内容寻址的 artifact 和一条只追加的 trace。 +**目录** · [一览](#一览) · [项目初衷](#项目初衷) · [亮点](#亮点) · [快速开始](#快速开始) · [设计随笔](#设计随笔) · [架构](#架构) · [文档](#文档) · [安全](#安全) · [许可证](#许可证) + +## 一览 + +Web UI —— 暗色优先、侧边栏导航,每个回答都能溯源到知识库: + +![OpsPilot web UI](docs/assets/webui.png) + +终端 UI —— 同一后端之上的斜杠命令 REPL: + +![OpsPilot TUI tour](docs/assets/tui.gif) + ## 项目初衷 AI 正在重塑整个 IT Support 行业。OpsPilot 是对一个具体问题的可运行回答: @@ -40,91 +52,53 @@ AI 正在重塑整个 IT Support 行业。OpsPilot 是对一个具体问题的 ## 亮点 - **多模型支持** —— Anthropic Claude、OpenAI、OpenRouter、Gemini、xAI Grok - 或本地 Ollama;playbook 声明主模型 + 可选备选模型(含本地 Gemma),可在 UI - 按次切换、也可在后台设团队默认,provider 出错时自动降级;管理员还能在后台 - 直接编辑 playbook 的可选模型列表(增删、升级模型名)。嵌入默认走 OpenAI, - 想留在内网的团队改一个环境变量即可切到本地 Ollama。playbook 还可按复杂度 - 分级路由,把占多数的简单任务交给便宜档位,只在需要时才升档 -- **工单接入(Intake)** —— 按 JQL 范围直接从 Jira Service Management 拉取 - 工单,AI 建议以评论形式发回工单本身:纯轮询(无需公网入口)、只发评论 - (绝不改动字段)、状态可跨重启保留,还有 `--replay` 模式离线演示完整闭环; - 接入管道可以指定比交互使用更便宜的模型,远程部署也可改用 - `POST /api/intake` 推送接入 + 或本地 Ollama;playbook 声明主模型 + 可选备选(含本地 Gemma),可按次切换 + 或设团队默认,provider 出错自动降级,复杂度分级路由把贵档位留给真正需要 + 的任务。管理员在后台就地编辑 playbook 的可选模型列表(增删、升级模型名); + 嵌入默认走 OpenAI,改一个环境变量即可切到本地 Ollama +- **工单接入(Intake)** —— 按 JQL 范围轮询 Jira Service Management,AI 建议 + 以评论形式发回工单本身:纯轮询(无需公网入口)、只发评论(绝不改动字段)、 + 状态跨重启保留,`--replay` 模式离线演示完整闭环;接入可用更便宜的模型, + 远程部署也可改用 `POST /api/intake` 推送 - **带引用的知识库检索** —— 向量(LanceDB)+ 全文(SQLite FTS5)混合搜索, RRF 融合;强模型走 `tool` 模式(ReAct),弱本地模型走 `prefetch` 注入 - **Memory(环境记忆)** —— 那些没有自己的表可放的环境事实:*"周二晚绝不重启 - ESXi 集群,财务在跑月结批处理"*。OpsPilot 的第二个自有领域。条目是**被准入 - 的,不是被抽取的** —— 人写下那句话和理由,因为抽取器分不清排查途中的一句 - 猜测和一个结论,而**一条错的条目从不报错,它只会安静地把助手带偏**。条目最 - 多带两个锚点(一台资产、一个站点),好让关于某个站点的约束回答不了另一个站 - 点的问题;被推翻时**追加**而不是就地改,所以"当初记错了"和"世界变了"始终分 - 得开;复核期过了只改变它出现时携带的标签,从不改变它是否适用。Memory 走自 - 己的检索路径而不是并入混合检索 —— 这正是助手能发现"一条记录的约束和一篇文 - 档在打架"、并开出一个 **Conflict** 交给人裁决的前提 + ESXi 集群"*。由人带着理由写下、最多带两个锚点(资产、站点)、被推翻时追加 + 而非就地改——并与知识库互相核对,两边打架时开出一个 **Conflict** + ([详情](#memory环境记忆)) - **Consultation(会话面)** —— 工程师真正排查问题的地方,背后是知识库、 - Memory 和 Skills。只有作者和管理员可见,90 天后清理 —— 正因为如此它才廉价 - 到可以在里面想出声。助手说的任何一句话都可以**在它被说出的当下**带着理由 - **pin 进 Memory**。**Working set** 承载"你正在追的那个问题"跨越一串对话, - 以及这个问题所在的地址 —— 那是带锚点的 Memory 能进入回答的唯一途径。它手动 - 关闭,外加一个会自报的无条件闲置兜底,因为**问题解决的那一刻没有人会回来点 - "关闭"**。要对一次对话的结论**动手**,它会升级成一个 Session,只携带一份工 - 单描述,别的什么都不带 -- **资产盘存** —— 从采购到退役追踪团队管理的设备,也是 OpsPilot 唯一*自己 - 持有*而非镜像的领域:小团队本就没有 CMDB,所以 CSV 导入导出既是迁入路径也 - 是迁出路径。八个自由设置的状态(不是状态机——真实盘存里到处是补录和更正)、 - 只追加且行为人取自认证调用方的事件日志,以及能从服务请求直接起草资产的 - 履约 playbook -- **运行时 Skills** —— 可复用的 `SKILL.md` 包,助手按需加载:它只看到一份 - 紧凑的触发条件目录,遇到匹配的问题才拉入完整流程;工具调用能力弱的模型 - 回退到检索注入。管理员可以让模型根据一段问题描述起草,也可以从一个**已关闭 - 的 Working set** 蒸馏 —— 一个被打开、跨越数次对话推进、然后收尾的问题。草稿 - **保留死路**,因为"先排除什么、按什么顺序"才是一个流程有用的那一半;而它把 - 停止条件和工具清单**刻意留空**:一次顺利的过程从未触碰过这两样,而一个看似 - 合理的猜测会被扫一眼就合并,空字段不会。**到达不等于准入** —— 把草稿移进 - `agent_skills/` 的那次 commit 才是准入 + Memory 和 Skills;助手说的任何一句话都能当场 pin 进 Memory,**Working set** + 承载"你正在追的问题"跨越一串对话([详情](#consultation会话面)) +- **资产盘存** —— 从采购到退役追踪团队管理的设备,CSV 双向迁移、自由设置的 + 状态、只追加的事件日志([详情](#资产盘存)) +- **运行时 Skills** —— 可复用的 `SKILL.md` 包,助手从紧凑的触发条件目录按需 + 加载;可从一段问题描述起草,或从已关闭的 Working set 蒸馏,唯有 commit + 才算准入([详情](#运行时-skills)) - **脱敏优先** —— 任何内容进入模型或知识库之前先剥离 PII - **可审计会话** —— 内容寻址 artifact、只追加 trace、schema 校验输出、可 浏览的历史记录。行为人取自认证调用方,而非调用方自报 -- **提议动作** —— 一次会话可以提出一条**只读诊断**,附带它的 dry-run 预览和 - 审批门的裁决,而**它只有在人按下执行时才会跑**;请求、预览、裁决、执行人和 - 结果全部追加进这次会话的 trace。第一批只有诊断、不含任何变更 —— 这个约束 - **写在 artifact schema 里**,intent 是一个常量,所以变更类动作**根本无法被 - 表达**。将来放开它,是一次可见、可评审的 diff。执行发生在加固 Docker(L2) - 或 gVisor(L3,fail-closed)容器中;审批门标记危险模式,但它是纵深防御信号 - **而不是边界** —— 边界是沙箱 +- **提议动作** —— 一次会话可以提出一条**只读诊断**,附带 dry-run 预览;只有 + 在人按下执行时才会跑,且在加固 Docker(L2)或 gVisor(L3)沙箱内执行 + ([详情](#提议动作)) - **复利式 wiki** —— 会话洞见蒸馏为经过 lint 检查、有生命周期管理的 wiki 页面,沉淀在长期知识库之上 -- **知识包导入导出** —— 把知识库、Skills、wiki 页面和 Memory 导出为一个归档, - 在别处还原。**各域用自己的原生格式**,不是统一信封:Skills 和 wiki 页面保持 - 文件形态,因为一个 Skill 要通过 pull request 准入,而 pull request 必须能被 - 当作 diff 阅读。**向量不随行** —— 它们绑定嵌入模型,由接收方重新 ingest。 - Session 和 Consultation **刻意没有**导出接口:一份只追加的账本,在它变成任 - 何人都能编辑的文件的那一刻就不再是账本了 +- **知识包导入导出** —— 把知识库、Skills、wiki 页面和 Memory 导出为一个 + 归档,在别处还原;各域用自己的原生格式,向量不随行 + ([详情](#知识包导入导出)) - **MCP 客户端** —— 任意 Model Context Protocol 服务器(stdio/HTTP)的工具 注入 ReAct 循环,按服务器配置允许/拒绝列表 - **界面与渠道** —— CLI、REPL 终端 UI(Textual,斜杠命令)、多标签 Web UI(Svelte 5,含知识库增强聊天)、FastAPI 后端;Telegram 渠道把知识库 - 问答带进你的聊天软件,还能用 `/intake` 直接立工作项;企业微信双向接入—— - 群机器人推送收单建议(通知模式),自建应用在聊天里回答知识库问题(对话模式) -- **多用户与 SSO** —— 登录门控的 Web UI,三种角色(viewer / operator / - admin);本地账号、LDAP / Active Directory 或 OIDC SSO 认证,支持组→角色 - 映射,后台模块管理用户/角色/认证源状态/审计。机器调用方(渠道、收单)走 - Service token;密钥只在环境变量、绝不入库。打包为 all-in-one Docker 镜像—— - 一条 `docker run` 就是带登录的完整工作台 + 问答和 `/intake` 立单带进你的聊天软件,企业微信双向接入——群机器人推送 + 收单建议(通知模式),自建应用在聊天里回答知识库问题(对话模式) +- **多用户与 SSO** —— 三种角色(viewer / operator / admin),本地账号、 + LDAP/AD 或 OIDC SSO 认证及组→角色映射,后台模块管理用户/角色/认证源状态/ + 审计;机器调用方走 Service token,密钥只在环境变量、绝不入库,all-in-one + Docker 镜像让一条 `docker run` 就是带登录的完整工作台 - **可观测性** —— Prometheus `/metrics`、OTel 兼容 JSON 日志、`/health` - **Rust 热路径** —— 分块器(~10×)和分词器(~45×)经 PyO3/maturin 编译, 纯 Python 透明降级;CI 门槛 ≥5× -## 一览 - -Web UI —— 暗色优先、侧边栏导航,每个回答都能溯源到知识库: - -![OpsPilot web UI](docs/assets/webui.png) - -终端 UI —— 同一后端之上的斜杠命令 REPL: - -![OpsPilot TUI tour](docs/assets/tui.gif) - ## 快速开始 ### 前置条件 @@ -226,6 +200,71 @@ docker run -p 8000:8000 \ 多服务部署(nginx TLS 终结、JSM 接入、可选 Ollama)见 [Docker Compose](docs/deployment.md#docker-compose)。 +## 设计随笔 + +上面的一行式亮点压缩掉了大量刻意的设计。这一节为 OpsPilot 自有的领域和 +那些容易做错的决定保留完整的推理过程。 + +### Memory(环境记忆) + +那些没有自己的表可放的环境事实:*"周二晚绝不重启 ESXi 集群,财务在跑月结 +批处理"*。OpsPilot 的第二个自有领域。条目是**被准入的,不是被抽取的** —— +人写下那句话和理由,因为抽取器分不清排查途中的一句猜测和一个结论,而**一条 +错的条目从不报错,它只会安静地把助手带偏**。条目最多带两个锚点(一台资产、 +一个站点),好让关于某个站点的约束回答不了另一个站点的问题;被推翻时**追加** +而不是就地改,所以"当初记错了"和"世界变了"始终分得开;复核期过了只改变它 +出现时携带的标签,从不改变它是否适用。Memory 走自己的检索路径而不是并入 +混合检索 —— 这正是助手能发现"一条记录的约束和一篇文档在打架"、并开出一个 +**Conflict** 交给人裁决的前提。 + +### Consultation(会话面) + +工程师真正排查问题的地方,背后是知识库、Memory 和 Skills。只有作者和管理员 +可见,90 天后清理 —— 正因为如此它才廉价到可以在里面想出声。助手说的任何 +一句话都可以**在它被说出的当下**带着理由 **pin 进 Memory**。**Working set** +承载"你正在追的那个问题"跨越一串对话,以及这个问题所在的地址 —— 那是带锚点 +的 Memory 能进入回答的唯一途径。它手动关闭,外加一个会自报的无条件闲置 +兜底,因为**问题解决的那一刻没有人会回来点"关闭"**。要对一次对话的结论 +**动手**,它会升级成一个 Session,只携带一份工单描述,别的什么都不带。 + +### 资产盘存 + +从采购到退役追踪团队管理的设备,也是 OpsPilot 第一个*自己持有*而非镜像的 +领域:小团队本就没有 CMDB,所以 CSV 导入导出既是迁入路径也是迁出路径。 +八个自由设置的状态(不是状态机——真实盘存里到处是补录和更正)、只追加且 +行为人取自认证调用方的事件日志,以及能从服务请求直接起草资产的履约 +playbook。 + +### 运行时 Skills + +可复用的 `SKILL.md` 包,助手按需加载:它只看到一份紧凑的触发条件目录,遇到 +匹配的问题才拉入完整流程;工具调用能力弱的模型回退到检索注入。管理员可以让 +模型根据一段问题描述起草,也可以从一个**已关闭的 Working set** 蒸馏 —— 一个 +被打开、跨越数次对话推进、然后收尾的问题。草稿**保留死路**,因为"先排除 +什么、按什么顺序"才是一个流程有用的那一半;而它把停止条件和工具清单**刻意 +留空**:一次顺利的过程从未触碰过这两样,而一个看似合理的猜测会被扫一眼就 +合并,空字段不会。**到达不等于准入** —— 把草稿移进 `agent_skills/` 的那次 +commit 才是准入。 + +### 提议动作 + +一次会话可以提出一条**只读诊断**,附带它的 dry-run 预览和审批门的裁决,而 +**它只有在人按下执行时才会跑**;请求、预览、裁决、执行人和结果全部追加进 +这次会话的 trace。第一批只有诊断、不含任何变更 —— 这个约束**写在 artifact +schema 里**,intent 是一个常量,所以变更类动作**根本无法被表达**。将来放开 +它,是一次可见、可评审的 diff。执行发生在加固 Docker(L2)或 gVisor(L3, +fail-closed)容器中;审批门标记危险模式,但它是纵深防御信号**而不是边界** +—— 边界是沙箱。 + +### 知识包导入导出 + +把知识库、Skills、wiki 页面和 Memory 导出为一个归档,在别处还原。**各域用 +自己的原生格式**,不是统一信封:Skills 和 wiki 页面保持文件形态,因为一个 +Skill 要通过 pull request 准入,而 pull request 必须能被当作 diff 阅读。 +**向量不随行** —— 它们绑定嵌入模型,由接收方重新 ingest。Session 和 +Consultation **刻意没有**导出接口:一份只追加的账本,在它变成任何人都能 +编辑的文件的那一刻就不再是账本了。 + ## 架构 ![OpsPilot 系统架构](docs/assets/architecture.png)