Skip to content

feat(harness): background drain ingestion, auto-context snapshot cache, and configurable reasoning-effort policy - #28

Open
LiuStar656 wants to merge 3 commits into
diqierjia:mainfrom
LiuStar656:feat/harness-background-drain
Open

feat(harness): background drain ingestion, auto-context snapshot cache, and configurable reasoning-effort policy#28
LiuStar656 wants to merge 3 commits into
diqierjia:mainfrom
LiuStar656:feat/harness-background-drain

Conversation

@LiuStar656

Copy link
Copy Markdown

feat(harness): background drain ingestion, auto-context snapshot cache, and configurable reasoning-effort policy

Problem

The DeepSeek Harness integration ingests memory synchronously: every completed
agent turn immediately runs StrataGate's LLM memory work (appendTurn, block
sealing, activated-memory retrieval) inline on the hot path, so conversation
assembly blocks on memory processing. The structured-call reasoning-effort
policy is also not user-configurable: force-off raises a desktop
notification and fails the structured call on providers that do not advertise
off.

Solution

Three commits, one concern each:

  1. da9ea51 — Background drain + snapshot cache (src/runtime.ts, src/index.ts, tests).
    Turns are folded on the hot path then only counted and queued; the actual
    LLM memory work runs on an unref()'d timer with per-session
    serialization, count-triggered eager draining (≥3 queued turns → 150 ms),
    and exponential backoff on failure. The system-prompt/assemble hook reads
    a synchronous snapshot cache refreshed by the drain instead of doing
    flush + retrieval inline; an empty or stale snapshot is skipped, or carries
    an explicit staleness marker. flush() stays the explicit synchronous
    point; a public refreshAutoContext(session) provides a deterministic
    refresh for tests and admin flows.
  2. 08600b8 — Effort degrade instead of failure (src/llm.ts, src/config.ts, tests).
    When the provider does not advertise off, force-off now falls back to
    the model default with a single warning instead of failing the structured
    call. The resolved config field is optional (default auto), so existing
    plugin configs and tests stay valid.
  3. a31d7c6 — Effort policy setting (src/client.js, src/index.ts, package.json, CHANGELOG).
    New @deepseek-ai/dsh-settings wiring: the effort policy (auto | force-off) is a user-editable switch on the plugin's settings page, bound
    through the client settings.section registration
    (register.inject carries hooks/setEffort/resetEffort) and mirrored into
    the model bridge on every change, so the current mode is read per structured
    call. Adds the peer/dev dependency and lockfile update; CHANGELOG gains the
    Unreleased entries.

Acceptance

  • npm run check --workspace stratagate-dsh — zero type errors.
  • npm run test --workspace stratagate-dsh — 59/59 passing (8 files),
    including new background-drain/snapshot-cache tests and the adapted
    plugin-composition test (assemble skips the memory context until the drain
    has rendered a snapshot).
  • Hot-path assemble is a synchronous cache read: no IO, never throws.
  • Backwards compatible: flush() still forces queued turns; config defaults
    unchanged; upstream multibatch protocol and turn-stopping are preserved.

中文说明

问题

DeepSeek Harness 集成的记忆摄取是同步的:每次回合结束都立即在热路径上运行
StrataGate 的 LLM 记忆处理(appendTurn、封存、激活记忆检索),对话组装会被
记忆工作阻塞;推理档位策略也不可配置,force-off 在模型不支持 off 时会发
桌面通知并使结构化调用失败。

方案(三个 commit,各管一事)

  1. da9ea51 后台计数驱动摄取 + 快照缓存(src/runtime.ts、src/index.ts、测试)。
    回合在热路径只折叠入队并计数;真正的 LLM 记忆工作在 unref() 定时器上执行,
    按会话串行,批量压力触发 eager drain(队列 ≥3 回合 → 150ms),失败指数退避。
    system-prompt/assemble 钩子改为读取后台 drain 渲染的同步快照缓存,空/过期
    快照直接跳过或带显式过时标记,热路径永不阻塞。flush() 仍是显式同步点;
    新增公开 refreshAutoContext(session) 供测试与管理流程确定性刷新。
  2. 08600b8 降级代替失败(src/llm.ts、src/config.ts、测试)。
    提供方不支持 off 时,force-off 退回模型默认档位并告警一次,不再失败。
    resolved 配置字段改为可选(默认 auto),现有插件配置与测试无需改动。
  3. a31d7c6 档位设置化(src/client.js、src/index.ts、package.json、CHANGELOG)。
    新接入 @deepseek-ai/dsh-settings:档位策略(auto | force-off)成为插件
    设置页的用户可编辑开关,经客户端 settings.section 注册
    register.inject 携带 hooks/setEffort/resetEffort)注入记忆 UI,改动即时
    镜像到模型桥,每次结构化调用按当前档位执行。新增 peer/dev 依赖与 lockfile
    更新;CHANGELOG 补充 Unreleased 条目。

验收

  • npm run check --workspace stratagate-dsh:零类型错误。
  • npm run test --workspace stratagate-dsh:59/59 通过(8 个文件),
    含新增的后台 drain/快照缓存测试与适配后的插件组装测试
    (assemble 在 drain 渲染出快照前跳过记忆上下文)。
  • 热路径 assemble 为同步缓存读:无 IO、永不 throw。
  • 向后兼容:flush() 仍强制消费积压回合;配置默认值不变;
    保留上游 multibatch 协议与 turn-stopping 行为。

Turns are folded on the hot path then only counted and queued; the actual
LLM memory work (appendTurn + auto-context rendering) runs on an unref'd
timer with per-session serialization, count-triggered eager draining and
exponential backoff. The system-prompt/assemble hook reads a synchronous
snapshot cache refreshed by the drain instead of doing flush + retrieval
inline, so hot-path assembly never blocks on StrataGate memory work.
Empty or stale snapshots are skipped or carry an explicit staleness marker.

Also adapts the plugin and runtime tests: assemble skips the memory context
until the background drain has rendered a snapshot, and tests wait for the
eager drain rather than an inline flush.
When the provider does not advertise off, force-off now falls back to the
model default and warns once, instead of raising a desktop notification and
failing the structured call. The effort field is optional in the resolved
config (default 'auto') so existing plugin configs and tests stay valid.
Wires @deepseek-ai/dsh-settings: the effort policy (auto | force-off) is a
user-editable switch on the plugin settings page, bound through the client
settings section (register.inject carries hooks/setEffort/resetEffort) and
mirrored into the model bridge on every change, so the current mode is read
per structured call. Adds the dsh-settings peer/dev dependency and updates
the lockfile; CHANGELOG gains the Unreleased entries for all three changes.

@diqierjia diqierjia left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢你对 StrataGate-AgentMemory 项目的支持,也感谢你投入时间实现后台 ingestion、自动上下文快照缓存和推理档位配置。这些改动解决的问题很有价值,PR 的结构和说明也比较完整。
我在 Node 24 环境下完成了本地验证:TypeScript 检查通过,现有 59/59 测试通过,构建和包校验也都成功。
不过,在补充失败路径和连续对话场景检查时,我发现了几个需要在合并前处理的问题:

  1. drain 在处理前删除队列,失败后可能永久漏写整批 turn,并影响后续显式 flush() 的恢复;
  2. 自动长期记忆快照根据上一轮问题生成,下一轮切换话题时可能注入不相关记忆;
  3. force-off 不支持时实际会重复告警,与“只告警一次”的说明不一致;
  4. capability lookup 失败时仍假定支持 off,可能触发原本希望避免的 provider 调用失败。
    其中失败后丢失 turn 是合并阻塞问题。请修复失败恢复逻辑并补充相应测试;自动上下文落后一轮的问题也请说明设计取舍或调整实现。
    每个对应的我都写在代码旁边了
    再次感谢你的贡献。完成修改后, review后没有问题就会合并,鲜花~。

const key = String(session.id)
const turns = this.pendingTurns.get(key)
if (!turns || turns.length === 0) return
this.pendingTurns.delete(key)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里在实际处理 turn 之前就删除了整个待处理队列。如果后续打开 memory space、调用模型或执行 appendTurn() 时发生临时失败,这批 turn 不会重新放回 pendingTurns;定时器虽然会再次运行,但届时队列已经为空,可能造成记忆永久漏写。
此外,失败的 drain Promise 会保留在 drainsTail 中,恢复前再次调用显式 flush() 仍可能继续收到同一个错误。
建议失败时按原顺序将未完成的 turn 重新入队,并确保 drain tail 在报告错误后能够恢复。请补充“首次 ingestion 失败,随后恢复并再次 flush”的回归测试

* throws; with no snapshot yet it returns '' so the assemble hook skips the
* memory context, and a stale snapshot carries an explicit marker.
*/
buildAutoContext(session: Session): string {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的同步缓存会让自动长期记忆与当前用户问题落后一轮。快照是在上一轮后台 drain 完成后,根据当时的 currentUserMessage(session) 生成的;下一轮 system-prompt/assemble 只读取旧快照,不会根据新的用户问题重新检索。
因此用户切换话题时,当前回答可能收到上一轮话题的记忆,而真正与当前问题相关的记忆要等到本轮结束后才生成。新会话第一轮通常也没有自动记忆。
请确认这是否是有意接受的功能降级。如果自动上下文需要针对当前问题召回,建议调整缓存设计,并增加“连续两轮使用不同主题”的测试。

}
const supportsOff = await this.modelSupportsReasoningEffort(route.provider, route.model, 'off')
if (supportsOff) return { ...route, reasoningEffort: 'off' as ReasoningEffortId }
this.ctx.logger.warn(`stratagate-memory force-off requested but provider "${route.provider}" model "${route.model}" does not support reasoning effort "off"; falling back to the model default`)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 说明和配置文案都写的是模型不支持 off 时“告警一次”,但这里每次结构化调用都会执行 logger.warn(),可能持续重复刷日志。
建议按 provider + model 记录已经告警的组合,只对同一模型告警一次,并增加对应测试。

} catch {
// Capability lookup is best-effort; if it fails, assume support so the
// request itself decides (a real rejection surfaces as a normal call error).
return true

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当 resolveModelInfo() 本身失败时,这里直接假定模型支持 off。这样仍会把 reasoningEffort: "off" 发送给能力未知的 provider,可能再次触发本次修改原本希望避免的调用失败。
建议能力检查失败时采用更保守的降级策略,例如不传 reasoningEffort,或者在请求被拒绝后进行一次受控降级重试。请补充 capability lookup 失败的测试。

@diqierjia

Copy link
Copy Markdown
Owner

@LiuStar656 I would like to confirm whether you still plan to address the issues mentioned in the review for this PR?

The issue where the “drain” operation may result in the loss of a turn is currently the cause of the merge being blocked. If you have been busy recently and don’t have time to continue working on it, that’s okay. Just let me know, and I can take over the fixing and then proceed with the merge.

Thank you!

@LiuStar656 想确认一下,这个 PR 你这边还打算继续处理 review 里提到的问题吗?

其中 drain 失败后可能丢 turn 是目前的合并阻塞项。如果你最近比较忙、暂时没时间继续处理也没关系,跟我说一声,我这边可以直接接手修复,然后继续推进合并。

感谢~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants