本提交是三件事的第一批,后续按计划分步推进。
## 一、判据改造(两条实证)
### 1. turn_hint 判据原是自证循环
原测试在测试里**手动重放**了真实注入逻辑:
f.Msgs = a.buildMessages(...)
hint := stageHint(f.StageCtx, "turn_hint")
f.Msgs = append(f.Msgs, Message{Role:"system", Content:"[Turn hints] "+hint})
// 然后断言"最后一条是 system、内容含 [Turn hints]"
它断言的是**它自己刚 append 的那一行** —— 把 task.go:656-661 抄进测试
再验证抄得对不对。真实代码那段删掉,它照样绿。
改为:把 turn_hint 放进 StageCtx,跑**真实的 stepPrepare**
(newTaskFrame 起始就是 StepPrepare),检查它产出的 Msgs。
变异验证:删掉 task.go 里 `if hint := stageHint(...)` 那段 ⇒ 实测红。
### 2. webui 文件下发判据原是白盒
原测试直接调未导出的 `stageWebFile(src, isImage)`、断言 `webFilesDir`
包级变量 —— 那都是**实现方式**。
改为从**输出通道 handler** 走(`RegisterOutputChannel("webui", …)`,
agent 发图/发文件时走的就是它):
输入:{payload: "<路径或URL>", type: "image"|"file"}
输出:{status:"ok", url:"/files/<name>.<ext>", size:N}
4 条判据(新版 handler_files_contract_test.go):
· 图片 → 可下载 URL(前缀/扩展名/size)
· 远端 URL 原样透传(不下载)
· 危险扩展名被中和(.html 保留会在 webui 域执行脚本)
· 空 payload 被拒
变异验证:丢扩展名 ⇒ 红;远端也拷贝 ⇒ 红。
## 二、文档立规:engineering-lessons 新增第五节
原来只有「判据会骗人」,没有「判据本身形状错了」。新增 6 小节:
5.1 判据必须把被测模块当黑箱(含"如果重写内部实现、接口不变,
这条判据还会绿吗"这个可操作判据)
5.2 禁止读源码做断言(附正确替代表)
5.2.1 判据不得以文档为依据
5.3 测试文件与业务代码分离(现状:521 文件 / 11.2 万行,
agent/core 一个包 196 文件里 144 个是测试)
5.4 CI 必须覆盖每个独立 module
5.5 CI 与本地必须跑同一套门
§5.2.1 的直接触发:`interrupt_invariant_test.go` 里有一条判据写着
「事实清单(来自探测,见 docs/zh/interrupt-subsystem-split-plan.md §1.5)」
—— 被引用方自己标着「状态:计划(未实施)」,而那条判据的实际断言
只是"入队后队列里 1 条",与声称的"反向依赖现状快照"毫无关系。
该判据已删除(入队行为已由 scheduler_preempt_test 的黑箱断言覆盖)。
## 三、CI 补两个缺口
### gofmt 门
提交纪律要求 `gofmt -l` 干净,但三个 workflow 里 `gofmt` 出现 **0 次**
—— 纪律只靠人自觉。补上后未格式化的代码进不了主干。
只查本仓自管目录:SDK 是独立仓,格式由它自己的 CI 管。
### SDK 判据步骤
`third_party/hologramagentsdk/` 有自己独立的 go.mod:
go list ./... | grep third_party → 0 个
主仓 `go test ./...` **永远不包含** SDK 任何测试。实测 SDK 有 30 个判据
文件,主仓 CI 一次都没跑过。
补的步骤只跑主仓 git 跟踪的文件(CI 上只有 22 个 vendored 文件,
工作机上是完整独立仓)—— 实测 `git archive` 出 22 个文件后
`go test ./sdk/ ./meta/` 全绿。
## 四、SDK 仓测试迁移(独立仓,已单独提交 3a38ef5)
`sdk/{plugin,proxy,stress}_test.go` → `sdk/test/`,包名 `sdk` → `test`。
关键:原测试直接写 `&PluginSDK{regStage: X, name: "test"}` —— **私有字段**。
而 `New(name, sett, regTool, regStage, regAPI, regOutput)` 本来就把所有
registrar 作为参数,等价且是生产路径。共改 19 处,0 处剩余。
主仓同步更新了跟踪路径(这三个文件主仓也 vendor 了一份)。
## 验证
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
go test ./... -count=1 62 包全过,0 FAIL
干净检出(worktree) build + 改造后的判据全绿
## 后续(按计划分步)
步骤 2 webui 29 个测试分类迁移(已实测出精确分类)
步骤 3 agent/core 739 处白盒引用先分类再动
步骤 4 SDK tools/hmapdev(package main,无法迁出,只加说明)
步骤 5 CI 补 coverage + staticcheck
步骤 6 文档双语(需定术语口径)
⚠️ AI-Assisted Programming Notice: Parts of this project's code, documentation, and commit history were generated or modified with AI assistance. Key changes have been human-reviewed, but please evaluate and verify before use.
HologramAgent
中文: README.md
Self-hosted agent kernel. Runs on your own hardware; plugins attach as separate processes.
make build build-cli
./build/homed -data /tmp/ha
API keys go in the web UI at http://localhost:8080. Requires Linux.
The main difference from typical agent frameworks is context handling: instead of truncating on overflow or asking the model to summarize, it scores history by relevance and sinks the low-scoring events into cold context, then distills those into a graph. Two other things differ: startup order is resolved at build time, and plugins launch as soon as their facilities are ready.
Architecture
homed (kernel)
LLM orchestration · memory · retrieval
│
┌──────────────┼──────────────┐
│ │ │
stdio RPC shared mem event ring
control bulk data notify
│ │ │
└──────────────┼──────────────┘
│
plugins (separate processes)
web UI · QQ · CLI · files · browser
The kernel process handles orchestration, memory and retrieval; HTTP, files and devices belong to
plugins (internal/plugin). A crashed plugin is restarted with backoff 1s / 2s / 3s; the 4th
crash within 5 minutes stops automatic restarting pending human intervention
(cmd/homed/kmod_runtime.go).
Context window
internal/agent/core/context.go
Each event gets two vectors on entry: a dense vector sharing one space with media and cold context, and a TF-IDF word vector as fallback. Pruning scores history against the current input and keeps topK, with the most recent N events exempt from scoring.
N defaults to 10, bounded by budget: min(configured, ContextTokens / avg tokens per event).
That bound came from a measurement. At a 50k window with 5800 tokens per event and a 40000 budget, pinning 10 events needed 58k, more than the budget itself. Every prune met its target without freeing space; the budget was gone after two rounds and the task terminated, with recall falling from 44% to 22%. Small windows now tighten automatically; a 1M window uses the configured value.
Pruned events move to the cold-context layer together with the media blocks they hold. Block IDs
stay the same; only the holding layer changes. Retrieval uses coldcontext_query: Peek to
confirm, then Remove the source copy. There is no fetch-and-delete path, since deletion follows
confirmation.
Three layers
internal/agent/core/context.go · internal/memory/coldcontext/ · internal/memory/graph.go
| Layer | Holds | Reaches the model | Cooling |
|---|---|---|---|
| L1 Context | events + blocks | directly, in the system prompt | low relevance sinks |
| L2 Cold Context | events + blocks (same shape as L1) | inserted back into L1 order on recall | ≤2 accesses and 72h idle, then distilled |
| L3 Stable | triples, nodes are blocks | on recall | never leaves |
A block has exactly one holder at any layer, with no reference counting. Images and audio are blocks in L2/L3 with their own vectors, searched in the same space as text.
Scenes
internal/memory/scene_emerge.go
A scene is built from five weighted features: channel 1.0, conversation partner 1.0, tools 0.8,
topic 0.4, time of day 0.2. A scene is created on the second occurrence of the same fingerprint
(minSceneEvidence = 2); one-off interactions don't create one. Join threshold 0.5, recall 0.35.
Recurrence adds 1 to strength; scenes that stop recurring decay by half-life.
Two entry points run in parallel: a plugin declares the scene at an injection point (e.g.
chan:qq/peer:group_1), or fingerprints cluster on their own. A declared scene derives features
only from its own key and does not absorb the full fingerprint. Absorbing it would make the
declared scene match nearly every later turn of that kind at close to 1.0 similarity, and the
clustering path could never grow narrower scenes.
Seven stages
internal/agent/core/stages.go
| Stage | When | Plugins can |
|---|---|---|
on_input |
message arrives | blocklist, rate limit, short-circuit reply |
pre_action |
context ready, before LLM | inject external data |
post_action |
LLM returns text and tool calls | filter, force redirect |
before_toolcall |
before one tool runs | audit, deny, rewrite args |
after_toolcall |
after one tool runs | redact, sort results |
before_output |
final text ready | format adaptation |
after_output |
after sending | statistics |
The inner loop is post_action → [before_toolcall → run → after_toolcall] → post_action. It exits
when the model stops calling tools, all calls are denied, or the limit is reached.
Everything else
- Channels: web UI, CLI, QQ groups and DMs, A2A, ACP in your IDE
- Tools: files, shell, code, browser (real Chrome), calendar, notes, RSS, music
- Modalities: images, video, voice, screenshots, web pages
28 built-in plugins, 89 model-callable tools. External plugins live one per repo under github.com/HologramAgent.
Downloads
Releases ship three variants:
| Variant | Contents | For |
|---|---|---|
| full | homed + waiter + desktop GUI + systemd unit | Single-machine, everything |
| server | homed + waiter + systemd unit | Servers (no desktop environment) |
| client | waiter + desktop GUI | Connecting to a remote HologramAgent |
- Linux:
.deb(amd64/arm64),.rpm(x86_64),.tar.gz - Windows:
HologramAgent_v1.2.0_{Full,Server,Client}_win64.exe(NSIS installer, includes the AGPL license page). Since v1.2.0homedno longer supports native Windows (it relies on fd inheritance and in-segment offset dereferencing), so the installer bootstraps WSL2 and installs the Linux packages inside the distribution the same way a Linux host would. - Portable:
homeagent-bin-<os>_<arch>.tar.gz(homed/waiter/initconfig) - Verification:
SHA256SUMS
The macOS homed requires a native macOS build (CGO + sqlite3), so release packages ship only waiter/initconfig.
Build
make build build-cli # Build daemon + CLI
make test # go test ./...
make install # Install to system
Dependencies: Go 1.25+, CGo (go-sqlite3), Linux.
homedrequires Linux (it relies on fd inheritance and intra-segment offset dereferencing of the shared memory region; seecmd/homed/platform_windows.go). On Windows onlywaiter.exeis built andhomedruns under WSL2 (see Downloads). macOS can buildwaiter/initconfig;homedmust be built on native macOS.
License
AGPL-3.0-only. See LICENSE.
Bundled third-party components keep their own licenses; full texts ship in
/usr/share/doc/hologramagent/licenses/.
Third-party components shipped with the packages
| Component | License | Location |
|---|---|---|
| Chinese-CLIP ViT-B/16 (ONNX artifacts) | Apache-2.0 | /usr/lib/hologram/models/chinese-clip-vit-b16-onnx/ |
ONNX Runtime (libonnxruntime.so) |
MIT | /usr/lib/hologram/onnxruntime/ |
| jieba dictionary (embedded in the binary) | MIT | internal/memory/jiebadict/ |
| Go dependencies (go-sqlite3, gojieba, bubbletea, …) | MIT / BSD-3 / Apache-2.0 | permissive, AGPL-3.0-compatible |
The package metadata declares this package as AGPL-3.0-only.