## 生产故障
all 2 providers failed, last error: api error 400:
"The reasoning_content in the thinking mode must be passed back to the API."
## 两个根因,第一个更根本
### ① thinking 关闭一直没生效(真正的触发器)
deepseek.lua 写的是 `req.extra_body.thinking = {type="disabled"}`。
但官方文档里那个 `extra_body={"thinking":...}` 是 **OpenAI SDK 层参数** ——
SDK 会把它摊平进 HTTP 请求体顶层。而适配器是**裸发** JSON 给 api.deepseek.com,
多包一层 `extra_body` 服务端根本不认,直接忽略。
⇒ 即使内核 thinking_enabled=false(发出 disable_thinking=true),
也从来没真正关掉过思考,每次都走 thinking 模式,每次都撞 reasoning_content 规则。
改为顶层 `req.thinking = {type="disabled"}`。
### ② reasoning 从未被保存和回传
DeepSeek 规则(官方 thinking_mode 指南):请求**带 tools** 时,
所有历史轮的 reasoning_content 都必须传回;内核每轮都带工具定义。
而内核三层全丢:
L1 ContextEvent 没有该字段;renderL1Messages 渲染 assistant 时不带
L2 coldcontext 只切 Input/Response 两块,推理内容不进块
L3 蒸馏原文只读 Input/Response/工具输出
修法(按用户裁定:L1/L2/L3 都要包含 reasoning):
L1 ContextEvent.ReasoningContent + renderL1Messages 回传
顺带:textForVector 纳入 reasoning —— 关键线索常只在思考里
("用户要的是 A 不是 B,所以先查 C"),只看 Response 会误裁
L2 RoleReasoning 独立块(不并进 Response:回灌时要放回不同字段,
合并会让思维链被当成正式回复发给模型)
L3 coldcontextOriginalText 按**时序** input → reasoning → response
## 一个易错点:不能用 f.Resp.ReasoningContent
f.Resp 只在「本批有工具调用」时赋值(stepLLM 里 len(ToolCalls)==0 就 return)。
纯文本收尾轮拿到的是**上一次工具轮**的旧响应 —— 用它写进 L1 会把上一批的
思维链冒名顶替成本轮的(比缺失更坏)。所以单独一个 TurnReasoning,
由 stepLLM 在分支**之前**无条件写入。
## 适配器兜底(治存量)
内核修了,但**存量会话**的 context.json 是修复前写的,那些事件没这个字段。
不兜底就是死结:历史缺字段 ⇒ 400 ⇒ 会话永久不可用(重启也救不回,
缺的字段在磁盘上的旧记录里)。
transform_request 里:带 tools 时给每条 assistant 补 reasoning_content="",
已存在则不覆盖(不抹掉真实思考内容)。
## 顺手修的既有缺陷
零时间戳时 docBlocksForEntry 用 time.Now() 算块 ID,而 ColdEvent.Timestamp
留零值 ⇒ 哈希永久失配 ⇒ 内容就在那里却一个字段都读不回,且不报错不记日志。
线上因为 L1 总带时间戳没暴露。docEventsFromEntries 里先归一化时间戳。
## 验证
生产实机(A2A 多轮工具调用,日志确认落到 deepseek 源直连):
LLM fallback: trying provider "deepseek"
response (12186ms, tools=[cmd_run plugin_start cmd_run])
reasoning_content 400 = 0 次
单元判据 16 条,全部变异验证(改回错误形态即红):
coldcontext/test/reasoning_contract_test.go 5 条(L2 往返/独立块/
无思考不造空块/特殊字符/InOut 兼容)
agent/api/test/reasoning_protocol_test.go 3 条(顶层字段/无思考不发/
带 tool_calls 仍发)
lua/test/deepseek_reasoning_test.go 8 条(补空串/不覆盖真实值/
无 tools 不补/补全部轮/不破其他字段/顶层 thinking/默认不加 thinking)
go build / vet / gofmt / go test -race 全绿
⚠️ 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.