lao-tseu-is-alive/Talunor

GitHub: lao-tseu-is-alive/Talunor

Talunor 是一个基于 Go 和 SQLite 的终端 AI agent 教学项目,通过分层迭代的代码结构和详细文档,演示如何从零构建具备多级记忆、工具调用和安全防护的认知循环 agent。

Stars: 0 | Forks: 0

# Talunor Talunor — terminal AI agent with long-term memory **Talunor** 是一个基于终端的自主决策 AI agent,使用 Go 构建, 其底层是由 SQLite 支持的多级 memory。它是**作为一个教学项目一步步开发出来的**: 每一层都很小、可运行且有详细文档,因此这个 repo 就像是一份指南, 教你如何构建一个带有 guardrails 的完整认知循环 agent (感知 → 推理 → 规划 → 行动 → 学习)。 ## 免构建运行 每个发布版本(git tag `vX.Y.Z`)都会提供两个预构建的、独立的 artifacts,因此 你无需 Go/C 工具链或 `make deps` 即可尝试任何迭代。它们同时捆绑了 SQLite 扩展**以及** embedding 模型,因此 memory 可以离线工作 —— 只有 **chat** 需要本地 [Ollama](https://ollama.com)(默认模型为 `qwen3:latest`)。仅限 Linux **amd64**(因为 sqliteai 扩展仅支持 x86_64)。 **容器镜像**(Docker,或者使用 Rancher Desktop 时的 `nerdctl` —— 命令相同): ``` # 容器通过 host.docker.internal 访问主机的 Ollama。-v 会跨运行保留 # 长期记忆。`docker run …` 是相同的。Port 11435 是 # 下方的安全桥接(快捷选项请使用 11434)。 nerdctl run --rm -it \ --add-host=host.docker.internal:host-gateway \ -e TALUNOR_OLLAMA_URL=http://host.docker.internal:11435/v1 \ -v talunor-data:/data \ ghcr.io/lao-tseu-is-alive/talunor:latest # 为 REPL 添加 --plain,使用 --list 10 检查记忆。将 :latest 替换为 # release tag(例如 :vX.Y.Z,参见 Releases 页面)以锁定特定版本。 ``` - **连接到 Ollama 需要一次性的主机设置。** Ollama 仅监听 `127.0.0.1`,而在 Rancher/Docker Desktop 下,容器运行在 VM 中 — 请参阅 **[将容器连接到 Ollama](docs/ollama-networking.md)**。 建议:将 Ollama 保留在 localhost,仅通过默认拒绝(default-drop)的防火墙桥接 VM (方案 A = socat + systemd,方案 B = 纯 nftables); 快速替代方案是将 Ollama 暴露给你的局域网。Linux 上的原生 Docker Engine 只需要 `--network host`。 - **TTY:** TUI 需要 `-it`。如果没有它,请使用 `--plain` 来运行行 REPL。 - 本地构建镜像:`make nerdctl-build && make nerdctl-run`(或者对应的 `docker-*` 命令);使用以下命令覆盖 endpoint: `make nerdctl-run OLLAMA_URL=http://host.docker.internal:11434/v1`。 **独立 bundle**(每个 GitHub Release 上的一个 `.tar.gz` 文件)。将 `vX.Y.Z` 替换为你从 [Releases](../../releases) 页面下载的 tag: ``` tar xzf talunor-vX.Y.Z-linux-amd64.tar.gz cd talunor-vX.Y.Z-linux-amd64 ./run.sh # TUI (./run.sh --plain for the REPL) ``` 该 bundle 需要主机上存在 `libstdc++6`(`ai.so` embedding runtime 依赖它); 容器镜像则不需要。请根据 `SHA256.txt` 校验下载的文件。 ## 为什么它很有趣 - **Embeddings 在 SQLite *内部*运行。** GGUF 模型(`all-MiniLM-L6-v2`,384维) 由 [`sqlite-ai`](https://github.com/sqliteai/sqlite-ai) 扩展在进程内执行 — 无需外部 embedding 服务。 - **向量搜索只是 SQL。** [`sqlite-vector`](https://github.com/sqliteai/sqlite-vector) 扩展将 embeddings 存储为 `FLOAT32` BLOBs 并基于它们执行 KNN。 - **一个文件就是整个大脑。** agent 的长期 memory 是一个单一的 SQLite 数据库文件。 - **agent 编写自己的 memory。** 每次对话后,一个 *reflection* 步骤会要求 模型从你所说的话中提炼持久的事实(“用户最喜欢的语言是 Go 和 TypeScript”),并将它们存储为 **semantic** memory, 与逐字记录的 **episodic** 对话分开 — 因此后续的问题会回忆起 干净的事实,而不是嘈杂的句子。 ## 架构(目标) ``` Perception ─► Memory recall (KNN) ─► Reasoning (LLM) ─► Action ─► Learning ▲ │ └───────────────── store ◄─────────────────────┘ internal/memory SQLite store + short-term ring buffer (embeddings, KNN) [Layers 1-2 ✓] internal/llm Provider interface + OpenAI-compatible adapter (Ollama) [Layer 3 ✓] internal/agent the cognitive loop (perceive→recall→reason→store) [Layer 4 ✓] internal/render shared streaming console renderer [✓] internal/tui Bubble Tea + Glamour front-end (default) [Layer 5 ✓] internal/version build identity [✓] cmd/doctor memory substrate smoke test [✓] cmd/chat LLM provider smoke test (streaming) [✓] cmd/talunor interactive agent REPL (persistent memory) [✓] ``` ## 状态 ### 迭代 1 — 对话 agent + memory | 层级 | 内容 | 状态 | |-------|------|--------| | 1 | **DB 基础** — 加载扩展,DB 内 embeddings,KNN | ✅ 完成 (v0.1.0) | | 2 | **Memory API** — `Remember` / `Recall` (KNN + 阈值),短期环形缓冲区 | ✅ 完成 (v0.2.0) | | 3 | **LLM provider** — `Provider` 接口 + Ollama (兼容 OpenAI) 适配器,流式传输 | ✅ 完成 (v0.3.0) | | 4 | **Agent 循环** — 感知 → 回忆 → 推理 → 存储 | ✅ 完成 (v0.4.0) | | 5 | **TUI** — Bubble Tea + Glamour(默认前端) | ✅ 完成 (v0.5.0) | **迭代 1 已完成** — Talunor 是一个可用的、支持 memory 增强的对话 agent。迭代 2 赋予了它 *行动* 的能力。 ### 迭代 2 — 工具与行动 | 层级 | 内容 | 状态 | |-------|------|--------| | 6 | **Providers 与配置** — OpenRouter provider,`llm.FromEnv()`,`.env` 加载器 | ✅ 完成 (v0.6.0) | | 7 | **工具与 ReAct 循环** — 工具注册表,原生 tool-calling,行动→观察循环 | ✅ 完成 (v0.7.0) | | 8 | **批准关卡** — 针对有副作用的工具进行人工介入的 y/n 批准(guardrail) | ✅ 完成 (v0.8.0) | | 9 | **沙箱化 `bash`** — 可插拔沙箱(namespaces/nerdctl),置于关卡之后 | ✅ 完成 (v0.9.0) | | 10 | **`web_fetch`** — 网络选入(opt-IN):受 SSRF 保护的 HTTP 获取,逐个 URL 批准 | ✅ 完成 (v0.10.0) | | 11 | **Memory 完整性与可观测性** — embedding 来源保护 + `--reembed`,内联 `/debug` 追踪 | ✅ 完成 (v0.11.0) | ### 迭代 3 — 规划与 guardrails | 层级 | 内容 | 状态 | |-------|------|--------| | 12 | **策略引擎** — 在每次工具调用前查询的 `Policy`(自动允许 / 批准 / 拒绝),`plan` 词汇表,通过 `TALUNOR_POLICY` 加载 YAML 规则文件 | ✅ 完成 (v0.12.0) | | 13 | **显式规划器** — 模型在执行多步行动前输出一个结构化、可检查的计划;策略会从整体和逐步对其进行拦截 | ⏳ 下一步 | ### 后续迭代 | 迭代 | 主题 | 增加内容 | |------|-------|------| | 4 | 学习 | memory 整合,显著性/衰减,异步 reflection | ## 环境要求 - Go 1.26+ - **`CGO_ENABLED=1`** 和 C 工具链 (gcc) — 因为 SQLite 扩展是 C 语言编写的。 - Linux x86_64(针对获取的扩展二进制文件;其他平台需要匹配的 发布 assets — 参见 `Makefile`)。 - 首次设置需下载约 52 MB(扩展 + embedding 模型)。 - 如需 chat:需要一个拉取了模型的本地 [Ollama](https://ollama.com) 服务器 (默认为 `qwen3:latest`)。执行 `make doctor` / `make test` 时不需要。 ## 快速开始 ``` make deps # fetch sqlite-vector, sqlite-ai and the embedding model into ext/ make doctor # smoke-test: Remember a corpus, Recall it by meaning make test # run the test suite ``` 预期的 `make doctor` 输出(略): ``` Talunor vX.Y.Z (commit …, built …) ✓ store open — embedding dimension = 384 • recall: "Which technology keeps a whole database in one file?" (threshold d≤0.75) 1. [d=0.2405] SQLite stores an entire relational database in a single file. • recall: "Tell me about a famous French landmark." (threshold d≤0.75) 1. [d=0.6189] The Eiffel Tower was completed in Paris in 1889. ``` 回忆起的 memory 是根据 *含义* 选择的(没有共享关键词),并且 相关性阈值会剔除所有无关内容 — 每个查询只返回与其 匹配的唯一 memory。 与本地模型对话(需要 Ollama 运行): ``` make chat PROMPT="In one sentence, what is vector similarity search?" # 或者: go run ./cmd/chat "your prompt" / echo "your prompt" | go run ./cmd/chat ``` 思考模型的推理过程会以暗淡的颜色流式传输,然后是全亮度的 回答 — 这直观地提醒你“推理”和“回答”是不同的。 使用 `TALUNOR_MODEL=qwen2.5-coder:14b` 覆盖模型。 运行交互式 agent — 一个使用 Glamour 渲染 markdown 的 Bubble Tea TUI。它 能跨轮次(并通过持久化的 `talunor.db` 跨会话)记住上下文: ``` make run # TUI (default) go run ./cmd/talunor --plain # minimal line-based REPL instead ``` 试着告诉它一些事情,然后在下一轮询问相关内容 — 甚至是 重启之后: ``` you> My name is Cedric and I love the Go programming language. talunor> Ah, Cedric! Go is a fantastic choice … you> What is my name and which language do I love? talunor> Your name is Cedric, and you love the Go programming language. ``` 第二个回答来自 memory:agent 回忆起之前的对话(短期 缓冲区 + 长期 KNN)并将其注入到 prompt 中。在 TUI 中, 思考模型的推理过程会以暗淡的颜色流式传输,然后回答会渲染为格式化的 markdown;**↑/↓ 回忆之前的 prompt**(类 shell 的历史记录),使用 PgUp/PgDn(或 Ctrl-U/Ctrl-D)滚动 对话记录,按 Ctrl-C 退出。鼠标被留给 用户自由操作,因此你可以点击拖动来选择和复制文本(例如用于分享对话记录); `--plain` REPL 同样支持完全选择和管道传输。 Prompt 历史记录是 **持久化且去重** 的:之前的 prompt(和斜杠 命令)可以通过 ↑/↓ 跨会话回忆,保持唯一性(重新提交一个 prompt 会将其提升为最新,而不是创建重复项),并存储在 数据库旁边的 `history.jsonl` 文件中。`--plain` REPL 会记录到同一个 文件,但由于它是基于 scanner 的,因此无法自身执行 ↑/↓ 行编辑。 ### 命令 TUI 和 `--plain` REPL 都支持: | 命令 | 效果 | |---------|--------| | `/help` | 列出命令 | | `/mem` | memory 统计信息(数量 + 数据库文件) | | `/list [n]` | 列出最近的 `n` 条 memory(默认为 10) | | `/forget ` | 删除具有该 `#id` 的 memory(如 `/list` 所示) | | `/clear` | 清除屏幕上的对话记录(仅限 TUI;不会擦除 memory) | | `/exit`, `/quit` | 退出 | 无需启动会话即可检查存储的 memory: ``` go run ./cmd/talunor --list 20 # dump the 20 most recent memories and exit ``` ### 选择模型 provider Chat 基于 **Ollama**(本地,默认)或 **OpenRouter**(托管的前沿 模型)运行;embeddings 始终在本地使用捆绑的模型运行。通过 `TALUNOR_PROVIDER` 选择: ``` # 本地 Ollama(默认)— 无需设置。 TALUNOR_MODEL=qwen2.5-coder:14b talunor # OpenRouter — 一个 frontier model: TALUNOR_PROVIDER=openrouter \ TALUNOR_MODEL=anthropic/claude-sonnet-4 \ OPENROUTER_API_KEY=sk-or-... \ talunor ``` 通过 `.env` 文件进行配置最为简便:**`cp .env_sample .env`** 然后进行编辑 — Talunor 会在启动时自动加载它(实际的环境变量仍具有更高优先级)。每个 支持的变量都在 [`.env_sample`](.env_sample) 中有详细说明。在付费的 provider 上,设置 `TALUNOR_REFLECT=0` 以跳过每轮对话的 reflection 调用。 ### 工具(ReAct 循环) 在每一轮中,Talunor 都会向模型提供一组工具,并运行 act→observe 循环: 模型请求调用某个工具,Talunor 运行该工具并将结果反馈,如此 重复直到模型给出答案。内置工具: - **`calculator`** — 安全的算术运算(经过解析,从不使用 `eval`), - **`current_time`** — 可选时区下的当前时间, - **`recall_memory`** — 按需搜索 Talunor 自己的长期 memory。 在回答之前,工具活动会以暗淡的注释流式显示(`🔧 calculator(…)` / `↳ 84`)。 使用 **原生 tool-calling**,因此 chat 模型必须支持此功能(qwen3 和大多数 OpenRouter 前沿模型都支持);如果模型不支持,请设置 `TALUNOR_TOOLS=0`。 ### 沙箱化 `bash` 工具(选入) 设置 `TALUNOR_BASH=1` 以添加一个 **`bash`** 工具,它将在一个 一次性的沙箱中运行 shell 命令,具有 **无网络** 和 **无主机文件系统** 权限 — 只有 `/tmp` 可写,并且在命令结束时会丢弃所有内容。它在默认情况下是关闭的,并且 由于它实现了 v0.8.0 批准接口,**每次调用都会在执行任何操作之前暂停,等待你明确的 y/N**。 两种可插拔的后端(通过 `TALUNOR_SANDBOX` 选择,或让其自动检测): - **`nerdctl`** — 委托给真实的 OCI runtime(`nerdctl` 或 `docker`;Rancher Desktop 也可以)。这是**强隔离**选项:seccomp、cgroups 和降权 capabilities 都是现成的。以 `--network none --read-only` 运行,并带有 `--pids-limit`、 `--memory`容量受限的 `--tmpfs /tmp`。当存在 runtime 时首选此选项。 - **`namespaces`** — 一个从头构建的、**无根** 的 Linux 沙箱,直接基于 user + mount + pid + net namespaces 构建:它将 Talunor 自己的二进制文件作为容器 init 进程重新执行,`pivot_root` 到缓存的 busybox rootfs(只读),挂载 全新的 `/proc` 和容量受限的 `/tmp`,设置 `no_new_privs`,丢弃所有 capabilities,并应用 rlimits。一个**空的 net namespace** 就是它没有网络的原因。这个后端是 **纵深防御和教学产物,而不是 一个严格的边界** — *没有 seccomp 过滤*,因此整个 syscall 表面 都是可达的,并且进程数限制是尽力而为(无根 cgroup 委派通常不可用; 内存上限 + 硬超时可抑制 fork 炸弹)。对于真正不受信任的代码,请使用 `nerdctl` 后端。 `namespaces` 后端仅限 Linux,并且需要启用 **非特权 user namespaces**。 在 Ubuntu 24.04+ 上,它们默认受到 AppArmor 限制;Talunor 会检测到 这一点,并告诉你解除限制(或者直接使用 `nerdctl` 后端)。 辅助脚本 [`scripts/allow-unprivileged-userns.sh`](scripts/allow-unprivileged-userns.sh) 可以为你切换它(`--restore` 恢复,`--status` 显示当前状态)。 如果无法建立沙箱,该工具将被跳过并发出警告 — 应用程序 仍会启动。 对于容器镜像,[`scripts/run-container-with-ollama-bridge.sh`](scripts/run-container-with-ollama-bridge.sh) 将启动 loopback→VM Ollama 桥接,并使用正确的 `nerdctl` 标志运行容器,只需一步完成(参见 [docs/ollama-networking.md](docs/ollama-networking.md))。 ### `web_fetch` 工具(选入) 设置 `TALUNOR_WEBFETCH=1` 以添加一个 **`web_fetch`** 工具,它读取 http(s) URL 并将其作为文本返回(网页、文档或 JSON API)。它是 **网络的选入**(opt-IN) — 与关闭网络的 `bash` 相反。它在默认情况下是关闭的,并且 **受批准关卡控制**:每次调用都会在显示 URL 时暂停,等待你的 y/N。 `bash` 需要 *内核* 沙箱(它运行代码),而 `web_fetch` 需要 *应用层* 策略(字节只是交给模型的文本),因此真正的 风险是 **SSRF**。该工具拒绝连接到 **私有、loopback、 link-local、云元数据(`169.254.169.254`)或 CGNAT** 地址 — 并且它 *在连接前立即*检查解析后的 IP,包括在初始请求和 每次重定向时,因此恶意的 DNS 响应或公共→内部的重定向都无法 混入。响应有 **大小限制**(512 KiB)并具有严格的超时,仅允许 `http`/`https`,非文本内容仅通过元数据报告 (模型的上下文中不包含二进制 blobs)。 `TALUNOR_WEBFETCH_ALLOW=example.com,.trusted.org` 列出了 **跳过批准提示** 的主机 (完全匹配的主机,或以点开头匹配子域名)。允许列表只会跳过 *提示* — SSRF 防护依然适用,因此解析为内部地址的“允许”主机 仍会被拒绝。通过 `TALUNOR_WEBFETCH_MAX_BYTES` 和 `TALUNOR_WEBFETCH_TIMEOUT` 进行调整(例如 `15s`)。 ### memory 存放在哪里 长期 memory 是一个单一的 SQLite 文件。其位置为 `$TALUNOR_DB`,如果未设置则为 `$XDG_DATA_HOME/talunor/talunor.db`,如果仍未设置则为 `~/.local/share/talunor/talunor.db`(自动创建) — 因此无论你从哪里启动,它都能跨会话 持久化。启动时会打印出 活动路径。持久化的 prompt 历史记录(`history.jsonl`,通过 ↑/↓ 回忆) 存在于同一个目录中。 ### 环境变量 | 变量 | 用途 | 默认值 | |----------|---------|---------| | `TALUNOR_PROVIDER` | chat 后端:`ollama` 或 `openrouter` | `ollama` | | `TALUNOR_MODEL` | 选定 provider 的模型 | provider 默认值 | | `TALUNOR_REFLECT` | 设为 `0` 以禁用按轮次的事实 reflection | `1` | | `TALUNOR_TOOLS` | 设为 `0` 以禁用工具(针对不支持工具的模型) | `1` | | `TALUNOR_POLICY` | 拦截工具调用的 YAML 规则文件路径(允许 / 提示 / 拒绝);未设置 = 每个工具的默认关卡 | — | | `TALUNOR_BASH` | 设为 `1` 以启用沙箱化、受批准关卡控制的 `bash` 工具 | `0` | | `TALUNOR_DEBUG` | 追踪 recall/tools/reflection:`1` → 记录到 DB 旁边的日志文件、`stderr` 或指定路径 | 关闭 | | `TALUNOR_SANDBOX` | bash 后端:`nerdctl` 或 `namespaces`(未设置 = 自动检测) | 自动 | | `TALUNOR_SANDBOX_IMAGE` | `nerdctl` 后端使用的镜像 | `alpine:3.20` | | `TALUNOR_SANDBOX_ROOTFS` / `TALUNOR_SANDBOX_BUSYBOX` | `namespaces` 后端使用的 rootfs 目录 / busybox | 基于静态 busybox 构建 | | `TALUNOR_WEBFETCH` | 设为 `1` 以启用受 SSRF 防护、受批准关卡控制的 `web_fetch` 工具 | `0` | | `TALUNOR_WEBFETCH_ALLOW` | 跳过获取批准提示的主机(逗号分隔;`.host` = 子域名) | — | | `TALUNOR_WEBFETCH_MAX_BYTES` / `TALUNOR_WEBFETCH_TIMEOUT` | 获取主体限制 / 请求超时(例如 `15s`) | `524288` / `10s` | | `TALUNOR_OLLAMA_URL` | 兼容 OpenAI 的 Ollama 基础 URL | `http://localhost:11434/v1` | | `OPENROUTER_API_KEY` | `openrouter` 必需 | — | | `TALUNOR_OPENROUTER_URL` | OpenRouter 基础 URL | `https://openrouter.ai/api/v1` | | `TALUNOR_DB` | 数据库文件 | 特定用户的数据目录(见上) | | `TALUNOR_VECTOR_EXT` / `TALUNOR_AI_EXT` / `TALUNOR_EMBED_MODEL` | 扩展 / 模型路径 | 位于 `ext/` 下 | 请参阅 [`.env_sample`](.env_sample) 获取可直接复制粘贴的起点。 ## 目前的经验总结 各个版本的完整细节见 [CHANGELOG.md](CHANGELOG.md)。重点如下: **层级 5(TUI)** - 流式 channel 能清晰地映射到 Bubble Tea 的 `Cmd`/`Msg` 模型:每个 命令对应一个 chunk,在每次更新时重新发出 — 没有后台 goroutine 修改 model,也不需要 mutex。 - 在流式传输时渲染原始文本,完成时运行一次 Glamour — 既流畅 *又* 正确(层级 3 的推理/回答分离,现在变为可视化)。 - TUI 无需终端即可测试:通过 `Update` 传递合成的 `tea.Msg` 并泵送返回的 `Cmd`s。 **层级 4(agent 循环)** - **循环顺序是一个正确性问题**:recall 必须在输入被存储 *之前* 发生, 否则 KNN 会将当前消息作为其自身的最佳匹配返回。 - 流式传输和“学习”通过一个 tee goroutine 共存 — 用户可以实时看到 token, 同时完成的对话会被捕获一次以供存储。 - 助手的对话仅在正常完成时才被存储;取消或出错的 流绝对不能污染 memory。 **层级 3(LLM provider)** - **思考模型将推理与答案分离开来**:Ollama 在单独的 `reasoning` 字段中返回 qwen3 的思维链,因此较小的 `max_tokens` 可能会导致返回的答案为空,因为它将所有的预算都花在了思考上。 - 一个兼容 OpenAI 的适配器即可服务于 Ollama、OpenAI 和 OpenRouter;只有 Anthropic 需要自己的适配器。 - 流式传输是基础原语;阻塞(`Collect`)是在其之上构建的。 **层级 2(memory API)** - **相关性阈值**与 top-k 一样重要:普通的 KNN 总是返回 `k` 行,因此偏离主题的查询仍然会将无关的 memory 注入到 prompt 中。通过余弦距离进行过滤可以保持 recall 的精确。 - `INSERT … RETURNING` 可以在一次往返中获取新的 id + 时间戳。 **层级 1(DB 基础)** - `sqliteai/sqlite-vector` **不是** `vec0` 虚拟表 API(那是 独立的 `asg017/sqlite-vec`);它使用 BLOB 列 + `vector_full_scan`。 - `mattn/go-sqlite3` 的 `LoadExtension(lib, "")` 需要一个 **明确的入口点** (`sqlite3_vector_init`,`sqlite3_ai_init`),否则它会因为空的 `undefined symbol` 错误而失败。 - `vector.so` 依赖于 **位于全局符号作用域中的 libm** — 它在启动时 使用 `RTLD_GLOBAL` 进行 `dlopen` 加载。 - `sqlite-ai` 在创建 embedding 上下文时需要 `embedding_type=FLOAT32`。 ## 布局 ``` cmd/doctor/ memory substrate smoke test cmd/chat/ LLM provider smoke test (streaming) cmd/talunor/ interactive agent REPL (persistent memory) internal/memory/ SQLite store: extensions, in-DB embeddings, KNN internal/llm/ provider interface + OpenAI-compatible adapter internal/agent/ the cognitive loop internal/plan/ plan vocabulary (Plan / PlanStep / RiskLevel) internal/policy/ action guardrail: Policy interface + tool-gate / rule-engine internal/render/ shared streaming console renderer internal/tui/ Bubble Tea + Glamour front-end internal/history/ persistent, deduplicated prompt history (↑/↓ recall) internal/version/ build identity ext/ fetched .so extensions + GGUF model (gitignored) Makefile deps / doctor / chat / run / test / build / docker-* Dockerfile self-contained image (binary + extensions + model) docs/lessons/ hands-on course: a guided path through the tag-by-tag history docs/policy.sample.yaml commented example TALUNOR_POLICY rule file docs/atlas.md full annotated map of every tracked file (see below) docs/ollama-networking.md reaching a loopback Ollama from the container (secure) .github/workflows/ CI (build+test), Release (bundle), Docker-publish, CVE scan CHANGELOG.md version-by-version build log + lessons AGENTS.md orientation guide for AI/human contributors ``` 此结构图经过精简;要获取 **每个目录和文件的完整、带注释的映射** — 每个都带有一行用途说明 — 请参阅 **[`docs/atlas.md`](docs/atlas.md)**。 ## 供应链与 CI 两个刻意设定的、不对等的信任级别: - **获取的二进制文件通过校验和锁定。** `make deps` 会验证每个 SQLite 扩展和 embedding 模型的 SHA256 *之后* 才加载它们 — 这些 `.so` 文件在没有沙箱的情况下作为进程内的原生代码运行,因此被篡改或 不完整的下载会被拒绝执行,而不是被执行(参见 `Makefile`)。 - **GitHub Actions 通过 commit SHA 锁定 — 针对第三方 actions。** 任何来自 不受信任的发布者(`aquasecurity/*`,`softprops/*`,`docker/*`,……)的内容都被 锁定到了不可变的 commit 上,因为像 `@v4` 这样的可变 tag 随时可能被控制该 action 仓库的人 重新指向恶意代码(参见 2025 年 3 月的 `tj-actions/changed-files` 事件)。**第一方 `actions/*` 和 `github/codeql-action`** 特意保留在移动的主要 tag(`@v4`,`@v5`)上:它们由 GitHub 自身维护,如果在没有像 Dependabot 这样的机器人的情况下通过 SHA 锁定它们,只会为了消除一点微小的剩余风险而带来真实的更新繁琐。这是一个有意识的例外,不是疏忽 — 如果 repo 将来增加了自动 action 升级,可以重新审视这一点。 ## 许可证 参见 [LICENSE](LICENSE)。
标签:AI智能体, AI风险缓解, DLL 劫持, EVTX分析, Go, Ruby工具, SQLite, 大语言模型, 教学项目, 日志审计, 请求拦截