## 1. 工作式子的工具禁令收成单一事实来源 禁令此前写了两遍:定义侧(把工具从 childTools 滤掉)与执行侧 (模型硬调时拒绝)。两处名单必须逐字一致,任何一边落后就是真实缺口: 定义侧漏滤 ⇒ 模型看得见但不该能用(白耗 token,还会反复试) 执行侧漏拒 ⇒ 模型绕过定义直接调用**就能生效** 现在只有 workerForbidden 一张表,每条禁令把"匹配方式 + 类别 + 解释" 绑在一个结构体里;定义侧 workerToolAllowed、执行侧 workerToolReject 都从它派生。 上一版我用 reason 字符串 + 下标猜分类 + hint 查表做了三处间接, 属于新造的技术债,已重写为单表直查。 判据(均经变异验证): TestWorkerToolDenyListIsSingleSource 长出第二份名单 ⇒ 红(实测确认) TestWorkerToolAllowedMatchesReject 两侧判据漂移 ⇒ 红 TestWorkerToolRejectExplainsWhy 拒绝文案不可执行 ⇒ 红 ## 2. 删除零使用的抽象(不留技术债) R1–R3 引入了 ChildKind / Host / Deliverer / DeviceResolver / ToolSpec / ToolResult / DeclaredKinds / KindWorker / State / SetState / BumpRound / ChannelDisposition,实现过程中发现它们是假抽象: · 各类型要用载体 Agent 的大半能力,要满足接口就得在实现里类型断言回 *Agent · 接口比被抽象的东西还大 ⇒ 只是把复杂度换个地方放 · 实测:26 个导出符号里 20 个零使用、零实现 已删除 host.go 与 kind.go,并把"四种类型清单"和"转投驻留子为何不是类型" 这两个有价值的结论移进 kinds.go 的文档注释(知识保留,死代码删除)。 child 包现在只剩实际被用的 9 个符号:Kind / Child / Entry / Registry / StopDeps / ErrChildExists / ErrChildNotFound(+ 2 个 Kind 常量)。 ## 3. 修掉误导性签名 Entry.Dispose(policy ResidualPolicy) 里 policy **两个实现都不读** —— 残余处置实际走 ApplyResidual / FinishResidentWith,在 Dispose 之前就 已经完成。看签名会以为这里处理残余,是误导。已去掉该参数, 并删掉 child 包里重复定义的 ResidualPolicy(core 包已有一份,且多一个 stash)。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./internal/agent/core/ -count=1 ok 15.300s go test ./internal/agent/core/ -count=1 -race ok 18.040s 干净检出(git worktree)单独 build + vet + test PASS
⚠️ 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.