From ad537df2d588b058c79346db8f74fef2c8ffff30 Mon Sep 17 00:00:00 2001 From: Vicente Date: Tue, 1 Sep 2026 03:34:44 -0700 Subject: [PATCH] docs: the README shows the product before the philosophy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure both READMEs for scanability without losing a claim: - "A quick look" (web UI shot + the freshly regenerated TUI gif) moves up under the intro, so a visitor sees the product before 100 lines of text; a one-line Contents strip follows the intro. - Highlights condenses the six long-form bullets (Memory, Consultation, Asset inventory, Runtime Skills, Proposed actions, Knowledge bundles) to 2-4 line summaries with [details] anchors. - A new "Design notes" section after Quick start keeps the six essays verbatim — the reasoning is a selling point, it just no longer blocks the path to Quick start. A high-effort prose review of the diff surfaced eight condensation losses — dropped guarantees ("no public endpoint", "no field is ever touched", "never the database", "when a provider errors", group robot vs self-built app), one altered claim ("up to two anchors" had become "an Asset or site"), one stale adjacency (inventory as "the one domain OpsPilot owns" beside Memory as "second owned domain" — now "the first domain"), and a TOC omission — all fixed in both languages. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KXrF6HMB12V1hC9xFtooDE --- README.md | 227 ++++++++++++++++++++++++++++-------------------- README.zh-CN.md | 175 ++++++++++++++++++++++--------------- 2 files changed, 242 insertions(+), 160 deletions(-) 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)