## 为什么还要修(前面两个修复都不够) 今天已经修了两处:263b651并发批次 stepToolBatch 漏记 ToolCalls0ed3c89L1 预算截断切开 tool 配对 并用生产 context.json 验证历史已干净: 53 个事件,0 个"结果多于调用"的帧 但 A2A **新会话**(a2a_1793778753015670668)仍然报同一个 400: An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following...) 同时日志显示 `ratio=0.28`(远低于 1.25 阈值)、`skip oversized frame` 计数 0 ⇒ 既不是裁剪,也不是预算截断。 ## 定位:问题在 f.Msgs,不在 L1 历史 `f.Msgs` 是**多条路径**拼出来的: prepare 重建 buildMessages 中断恢复 rebaseFramePrefix(重建 prefix + 接回 tail) 批末附加 flushBatchAttachments 占位补全 dropContinuationPlaceholders / continuation 任何一条漏掉配对,最终都表现为同一个 400,而协议**不告诉你是哪条 call**。 所以把自检放在唯一一次组装 provider 请求的位置(stepLLM,task.go:750), 而不是散在各条路径上。 ## 为什么剔 call 而不是补占位 补一条 "tool result: (missing)" 会让模型看到它**从未调用过**的结果, 可能据此继续推理 —— 那是编造事实。剔掉 call 只是少了一次尝试, 模型可以重来,语义安全。 被剔的 call 记录工具名与数量,日志里能看出是哪一轮出的问题。 ## 判据(5 条,3 次变异全部实测变红) DropsCallWithoutResult 主方向:没有结果的 call 必须剔 变异:直接 return msgs ⇒ "★ 未配对的 call c2 仍在消息里 —— 上游必然 400" DropsOrphanToolResult 反方向:找不到 call 的孤立 tool 必须删 变异:删掉第二遍循环 ⇒ 红 DropsEmptyAssistantShell call 全剔且正文为空 ⇒ 整条删 变异:去掉该分支 ⇒ 红 KeepsContentWhenAllCallsDropped call 全剔但**有正文**时保留正文(那是模型说的话,不能丢) LeavesValidSequenceUntouched 合法序列必须逐条不变 —— 修正逻辑不能引入新问题 只测一个方向会放过另一半失配,所以两个方向都钉。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./... -count=1 62 包全过,0 FAIL 干净检出(worktree) build + TestDropUnpaired 全绿 ## 下一步 这条是**止损 + 定位**,不是根因修复。部署后如果日志里出现 "dropped N unpaired tool message(s)",那 N 与工具名就指向真正漏配对的 那条路径,据此再修上游。
⚠️ 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.