@aiwayds/dsh-topics-memory 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -1
- package/README.zh.md +69 -1
- package/lib/commands.js +3 -0
- package/lib/config.d.ts +44 -1
- package/lib/config.js +33 -2
- package/lib/consolidate.d.ts +39 -1
- package/lib/consolidate.js +195 -1
- package/lib/index.d.ts +10 -0
- package/lib/index.js +53 -2
- package/lib/jev/backends.d.ts +31 -0
- package/lib/jev/backends.js +46 -0
- package/lib/jev/client.d.ts +91 -0
- package/lib/jev/client.js +211 -0
- package/lib/jev/gate.d.ts +50 -0
- package/lib/jev/gate.js +125 -0
- package/lib/jev/log.d.ts +65 -0
- package/lib/jev/log.js +119 -0
- package/lib/jev/thresholds.d.ts +21 -0
- package/lib/jev/thresholds.js +27 -0
- package/lib/quality.d.ts +60 -2
- package/lib/quality.js +230 -11
- package/package.json +2 -2
- package/src/commands.ts +2 -0
- package/src/config.ts +41 -2
- package/src/consolidate.ts +238 -1
- package/src/index.ts +61 -2
- package/src/jev/backends.ts +66 -0
- package/src/jev/client.ts +318 -0
- package/src/jev/gate.ts +151 -0
- package/src/jev/log.ts +184 -0
- package/src/jev/thresholds.ts +31 -0
- package/src/quality.ts +269 -11
package/README.md
CHANGED
|
@@ -38,6 +38,78 @@ This plugin is not trying to be the model's notebook. It is trying to be the mod
|
|
|
38
38
|
- **Knowledge as a graph**: `depends` (machine-readable directed edges) plus body `[[wikilinks]]` and markdown links (human-written edges) form one graph; retrieval walks it in both directions (per-level decay, configurable depth) so a single hit pulls in a knowledge subgraph; every write rebuilds the `meta/backlinks.json` reverse index, and `/topics show` lists "who references me, and how" — check the blast radius before changing a conclusion.
|
|
39
39
|
- **Two-stage observer (M2)**: the main model jots atomic observations with `topic_observe`; a background distill lane (session end + every N turns, model configurable) distills them into formal Topics in batches; when the model itself deems something worth keeping, it `topic_save`s directly.
|
|
40
40
|
|
|
41
|
+
## Jev decision layer (System One, experimental, default off)
|
|
42
|
+
|
|
43
|
+
An optional decision layer on the asynchronous lanes, powered by a System One typed-decision model (jev): it batch-scores slow-lane rerank candidates (replacing the LLM rerank), pre-filters consolidation merge pairs before the LLM gardener sees them, and shadow-audits the fast-lane lexical gate from a turn-end lane. Every verdict is probability-gated, and any failure — timeout, network error, bad answer — falls back to today's pure-local behavior (hardcoded fail-open, no switch key). The injection hot path never makes a remote call (ADR 0016–0019; design doc: `docs/design/2026-09-25-system-one-integration.md`). Off by default: `jevEnabled: false` means zero behavior change.
|
|
44
|
+
|
|
45
|
+
### Keys
|
|
46
|
+
|
|
47
|
+
| Key | Default | Meaning |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `jevEnabled` | `false` | Master switch (rollout: default off → shadow → opt-in per profile → default-on observation); `false` stops the stats too |
|
|
50
|
+
| `jevBackend` | `zen` | `zen` (free) / `native` / `openrouter` |
|
|
51
|
+
| `jevModel` | empty sentinel | Resolved per backend at call time: `jev-1.13-free` (zen) / `jev-1.13.0` (native) / `typesafe/jev-1.13` (openrouter) — pinned, never an alias |
|
|
52
|
+
| `jevTimeoutMs` | `3000` | Per-request timeout; single `AbortSignal.timeout`, no retry (the lane's next cadence retries naturally) |
|
|
53
|
+
| `jevSecretFile` | none | Path to an external secret list; loaded once at boot, re-read when the path is hot-changed |
|
|
54
|
+
|
|
55
|
+
### Keys provisioning
|
|
56
|
+
|
|
57
|
+
| Backend | Key source |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `zen` (default, free) | `JEV_ZEN_API_KEY` env, or the macOS keychain service `opencode-zen-inference` (default fallback — zero config) |
|
|
60
|
+
| `native` | `TYPESAFE_API_KEY` |
|
|
61
|
+
| `openrouter` | `OPENROUTER_API_KEY`; the keychain service `openrouter-inference` requires an explicit `JEV_KEYCHAIN` |
|
|
62
|
+
|
|
63
|
+
dsh scrubs ambient `KEY|PASSWORD|SECRET|TOKEN` variables from plugin environments, so a key exported in your shell never reaches the plugin — pass it explicitly through the profile patch `env:` block, or keep it out of files entirely via the keychain (`JEV_KEYCHAIN` / zen's default service; neither matches the scrub rules):
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
# ~/.dsh/cordis.patch.yml — merge into your profile patch. The !!js
|
|
67
|
+
# expression keeps the key out of the file (same convention as dsh-jev-mcp).
|
|
68
|
+
- insert:
|
|
69
|
+
- id: dsh-topics-memory
|
|
70
|
+
name: '@aiwayds/dsh-topics-memory'
|
|
71
|
+
env:
|
|
72
|
+
JEV_ZEN_API_KEY: !!js process.env.JEV_ZEN_API_KEY ?? ''
|
|
73
|
+
# native: TYPESAFE_API_KEY: !!js process.env.TYPESAFE_API_KEY ?? ''
|
|
74
|
+
# openrouter: OPENROUTER_API_KEY: !!js process.env.OPENROUTER_API_KEY ?? ''
|
|
75
|
+
# plus JEV_KEYCHAIN: 'openrouter-inference'
|
|
76
|
+
# (zen needs no JEV_KEYCHAIN — with no env key it reads the
|
|
77
|
+
# default keychain service 'opencode-zen-inference')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Thresholds
|
|
81
|
+
|
|
82
|
+
Adopt / record / fallback bands (calibrated offline on a 383-case gold corpus; the numbers are bound to the batch protocol — design doc §5). Record-band verdicts only land in the decision log without affecting behavior. The cluster-level "worth consolidating?" question records without gating in v1:
|
|
83
|
+
|
|
84
|
+
| Seam | Adopt | Record-only | Fallback line |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| Slow-lane rerank (per candidate) | noul ≥ 0.60 → enters picks | 0.10 – 0.60 | < 0.10 → strong veto, lexical-gate behavior |
|
|
87
|
+
| Consolidation merge (per pair) | noul ≥ 0.50 → pair sent to the LLM | 0.15 – 0.50 (pair still evaluated by the LLM) | < 0.15 → pair cut before the LLM |
|
|
88
|
+
|
|
89
|
+
### Fallback behavior
|
|
90
|
+
|
|
91
|
+
| Seam | On failure / timeout / bad answer | Legacy path |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Slow-lane rerank | The round falls back to the old LLM rerank; picks still produced | Old RERANK_PROMPT path kept until rollout step 4 (default-on observation), then removed |
|
|
94
|
+
| Consolidation prefilter | The cluster goes to the LLM in full, exactly as today | none |
|
|
95
|
+
| Fast-lane shadow | Records the outcome only; zero behavior impact | none |
|
|
96
|
+
|
|
97
|
+
Every call and verdict lands in `~/.dsh/topics/meta/decisions.jsonl` — local-only and redacted: slugs, pair hashes, question types, probabilities, latency and token counts; never conversation text or conclusion bodies. It has no config key (it stops together with `jevEnabled: false`); `/topics status` shows a 30-day summary line.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Latency benchmarks (measured 2026-09-26, Apple M5, typesafe `native` backend, jev-1.13.0)
|
|
101
|
+
|
|
102
|
+
Real-payload benchmarks and in-sandbox runs — use them to pick `jevTimeoutMs`. Sources vary in shape and load; all are the same 8-candidate shadow batch unless noted:
|
|
103
|
+
|
|
104
|
+
| Source | Shape | p50 | p90 | max | n |
|
|
105
|
+
|---|---|---|---|---|---|
|
|
106
|
+
| Idle benchmark (sequential) | 8-question batch | 742 ms | 1290 ms | 1461 ms | 10 |
|
|
107
|
+
| Idle benchmark (sequential) | 1-question | 367 ms | — | 925 ms | 5 |
|
|
108
|
+
| Threshold-sweep runs (Sept 25) | 20-question batch | 1760 ms avg | — | 8470 ms | 16 |
|
|
109
|
+
| Live headless turns | 8-question batch, **concurrent with the main model streaming** | 327–5698 ms | — | 10619 ms | 3 |
|
|
110
|
+
|
|
111
|
+
Readings that matter: the idle path sits comfortably under the 3000 ms default (≈2× headroom at p90), but a live turn's shadow call shares the network with the main model's streaming response — the 10.6 s outlier above was observed exactly there. A timeout is fail-open: the batch is dropped (slow-lane rerank falls back to the old LLM path), so a tight timeout costs shadow data, never correctness. Keep the 3000 ms default unless `decisions.jsonl` shows a persistent `timeout` share above ~5% (the 30-day summary in `/topics status` surfaces it); weak-network users can raise `jevTimeoutMs` freely. `zen` and `openrouter` are unmeasured here — after enabling, your own decisions.jsonl latency column is the ground truth for your network.
|
|
112
|
+
|
|
41
113
|
## Quick start
|
|
42
114
|
|
|
43
115
|
1. Install (command below), restart dsh;
|
|
@@ -126,6 +198,11 @@ First-time setup belongs to `/topics onboard`; day-to-day tuning is `/topics set
|
|
|
126
198
|
| `deprecatedTtlDays` | `15` | Deprecated topics older than N days are dropped at session start (local rule, no model; each drop is its own git commit — history stays recoverable); `0` disables the sweep |
|
|
127
199
|
| `usageBoost` | `0.15` | Usage boost (ADR 0015): topics injected/opened in the last 30 days score higher at retrieval (gate-scoped, capped at 0.2, never granted to zero-lexical candidates, structural gate not waived); `0` disables |
|
|
128
200
|
| `pushDebounceSeconds` | `45` | GitHub-mode debounced push interval |
|
|
201
|
+
| `jevEnabled` | `false` | System One decision layer master switch (experimental): `false` = zero behavior change — see the Jev decision layer section above |
|
|
202
|
+
| `jevBackend` | `zen` | Decision endpoint: `zen` (free) / `native` / `openrouter` |
|
|
203
|
+
| `jevModel` | empty (per backend: `jev-1.13-free` / `jev-1.13.0` / `typesafe/jev-1.13`) | Version-pinned decision model; upgrading is an explicit action |
|
|
204
|
+
| `jevTimeoutMs` | `3000` | Per-request decision timeout; single attempt, no retry |
|
|
205
|
+
| `jevSecretFile` | none | External secret list for the outbound secret gate; re-read when hot-changed |
|
|
129
206
|
|
|
130
207
|
## Acknowledgements
|
|
131
208
|
|
|
@@ -148,7 +225,7 @@ This project's shape is directly inspired and supported by:
|
|
|
148
225
|
## Design docs
|
|
149
226
|
|
|
150
227
|
- [CONTEXT.md](CONTEXT.md) — domain glossary
|
|
151
|
-
- [docs/adr/](docs/adr/) — 0001–
|
|
228
|
+
- [docs/adr/](docs/adr/) — 0001–0019: OKF compliance, remote shape, sync strategy, two-stage observer, bundle layout, injection defaults, observability & tunables, dual-mode persistence, onboarding wizard, subagent isolation, the include-subagents switch, injection dedup default-on, the rename & migration, dual-channel injection, the usage boost, direct-HTTP decisions, the fast-lane zero-remote-calls line, hardcoded fail-open, the embedded secret gate
|
|
152
229
|
|
|
153
230
|
## License
|
|
154
231
|
|
package/README.zh.md
CHANGED
|
@@ -38,6 +38,68 @@ https://github.com/user-attachments/assets/6e9b346d-2ec9-4148-ae62-7087797d3188
|
|
|
38
38
|
- **知识连接成图**:`depends`(机器可读的有向依赖边)+ 正文 `[[wikilink]]` 与 markdown 链接(人写边)共同构成图;检索命中后沿图双向游走(每层衰减一半、深度可配),一次命中带入一个知识子图;每次写入自动重建 `meta/backlinks.json` 反向引用索引,`/topics show` 直接列出「谁引用了我、怎么引用的」——改一条结论前先看牵连面。
|
|
39
39
|
- **两段式观察(M2)**:主模型用 `topic_observe` 随手记原子观察,后台蒸馏 lane(session end + 每 N 轮,模型可配)把观察批量蒸馏成正式 Topic;主模型认为值得记时直接 `topic_save`。
|
|
40
40
|
|
|
41
|
+
## Jev 决策层(System One,实验,default off)
|
|
42
|
+
|
|
43
|
+
架在异步 lane 上的可选决策层,由 System One typed-decision 模型(jev)驱动:慢车道 rerank 候选改批量 noul 打分(替换 LLM rerank)、整理 lane 的 merge 候选对在 LLM 园丁接手前先做前置过滤、快道词法门由 turn-end lane 做影子对账。每个判定都过概率门控,任何失败——超时、网络错、坏答案——一律回退到今天的纯本地行为(fail-open 硬编码,无开关键)。注入热路径永不发起远程调用(ADR 0016–0019;设计文档:`docs/design/2026-09-25-system-one-integration.md`)。默认关闭:`jevEnabled: false` 即零行为变化。
|
|
44
|
+
|
|
45
|
+
### 键
|
|
46
|
+
|
|
47
|
+
| 键 | 默认 | 说明 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `jevEnabled` | `false` | 总开关(灰度:default off → shadow → opt-in per profile → 默认开观察);`false` 连统计一起停 |
|
|
50
|
+
| `jevBackend` | `zen` | `zen`(免费)/ `native` / `openrouter` |
|
|
51
|
+
| `jevModel` | 空串哨兵 | 调用时按 backend 解析:`jev-1.13-free`(zen)/ `jev-1.13.0`(native)/ `typesafe/jev-1.13`(openrouter)——钉版本号,永不指向别名 |
|
|
52
|
+
| `jevTimeoutMs` | `3000` | 单请求超时;单发 `AbortSignal.timeout` 不重试(lane 的下一节拍自然重试) |
|
|
53
|
+
| `jevSecretFile` | 无 | 外部 secret 清单路径;启动加载一次,热更换路径即重读 |
|
|
54
|
+
|
|
55
|
+
### key 配置
|
|
56
|
+
|
|
57
|
+
| Backend | key 来源 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `zen`(默认,免费) | `JEV_ZEN_API_KEY` env,或 macOS 钥匙串服务 `opencode-zen-inference`(默认回退——零配置) |
|
|
60
|
+
| `native` | `TYPESAFE_API_KEY` |
|
|
61
|
+
| `openrouter` | `OPENROUTER_API_KEY`;钥匙串服务 `openrouter-inference` 需显式 `JEV_KEYCHAIN` |
|
|
62
|
+
|
|
63
|
+
dsh 会清洗插件环境里匹配 `KEY|PASSWORD|SECRET|TOKEN` 的 ambient 变量,shell 里 export 的 key 到不了插件——请走 profile patch 的 `env:` 块显式透传,或用钥匙串(`JEV_KEYCHAIN` / zen 默认服务;都不匹配清洗规则)完全避开文件落 key:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
# ~/.dsh/cordis.patch.yml — 并入你的 profile patch。!!js 表达式让 key 不落文件
|
|
67
|
+
# (与 dsh-jev-mcp 同款约定)。
|
|
68
|
+
- insert:
|
|
69
|
+
- id: dsh-topics-memory
|
|
70
|
+
name: '@aiwayds/dsh-topics-memory'
|
|
71
|
+
env:
|
|
72
|
+
JEV_ZEN_API_KEY: !!js process.env.JEV_ZEN_API_KEY ?? ''
|
|
73
|
+
# native: TYPESAFE_API_KEY: !!js process.env.TYPESAFE_API_KEY ?? ''
|
|
74
|
+
# openrouter: OPENROUTER_API_KEY: !!js process.env.OPENROUTER_API_KEY ?? ''
|
|
75
|
+
# 另加 JEV_KEYCHAIN: 'openrouter-inference'
|
|
76
|
+
# (zen 无需 JEV_KEYCHAIN——无 env key 时自动读默认钥匙串服务)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 阈值(rerank / merge pair)
|
|
80
|
+
|
|
81
|
+
| 档 | rerank(慢车道) | merge pair(整理前置) | 动作 |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| 采纳 | noul ≥ 0.60 | ≥ 0.50 | 进 picks / 送 LLM 生成 |
|
|
84
|
+
| 记录带 | 0.10 – 0.60 | 0.15 – 0.50 | 只落 decisions.jsonl,不影响行为 |
|
|
85
|
+
| 回退 | < 0.10 | < 0.15 | 强否决,回退词法/现状行为 |
|
|
86
|
+
|
|
87
|
+
绝对线绑定批量协议(换协议必须重扫)。三缝失败回退行为见 ADR 0018。判定与调用明细落 `meta/decisions.jsonl`(本地 only,脱敏——绝不存 state 原文)。
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
### 延迟基准(2026-09-26 实测,Apple M5,typesafe `native` 后端,jev-1.13.0)
|
|
91
|
+
|
|
92
|
+
真实负载基准 + 沙箱实跑——用来选 `jevTimeoutMs`。各 source 形状与负载不同,除注明外均为同一 8 候选 shadow 批量:
|
|
93
|
+
|
|
94
|
+
| 来源 | 形状 | p50 | p90 | max | n |
|
|
95
|
+
|---|---|---|---|---|---|
|
|
96
|
+
| 空载基准(顺序发) | 8 问批量 | 742 ms | 1290 ms | 1461 ms | 10 |
|
|
97
|
+
| 空载基准(顺序发) | 单问 | 367 ms | — | 925 ms | 5 |
|
|
98
|
+
| 阈值扫描(09-25) | 20 问批量 | 均值 1760 ms | — | 8470 ms | 16 |
|
|
99
|
+
| 真机 headless 轮 | 8 问批量,**与主模型流式并发** | 327–5698 ms | — | 10619 ms | 3 |
|
|
100
|
+
|
|
101
|
+
关键读数:空载路径远低于 3000 ms 默认线(p90 约 2× 余量);但真机轮的 shadow 调用与主模型的流式响应共享网络——上表 10.6 s 离群值正是在这个位置观测到的。超时即 fail-open:该批数据丢弃(慢车道回退旧 LLM rerank),**紧超时损失的是 shadow 数据,不是正确性**。默认 3000 保持不变;除非 `decisions.jsonl` 显示 `timeout` 占比持续 >5%(`/topics status` 的 30 天摘要会露出),弱网用户可自行上调 `jevTimeoutMs`。`zen` 与 `openrouter` 本次未实测——开启后你自己 decisions.jsonl 的 latency 列就是你网络的真值。
|
|
102
|
+
|
|
41
103
|
## 工具与命令
|
|
42
104
|
|
|
43
105
|
| 模型工具 | 用途 |
|
|
@@ -126,6 +188,12 @@ dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory
|
|
|
126
188
|
| `deprecatedTtlDays` | `15` | deprecated 条目超过 N 天在会话启动时自动删除(本地规则不依赖模型,逐条 git commit 可回溯找回);`0` 关闭清扫 |
|
|
127
189
|
| `usageBoost` | `0.15` | 使用加成(ADR 0015):近 30 天被注入命中/点开过的 Topic 检索加分(计入门槛分、帽 0.2、零词面相关不加、结构门不豁免);`0` 关闭 |
|
|
128
190
|
| `pushDebounceSeconds` | `45` | GitHub 模式去抖推送间隔 |
|
|
191
|
+
| `jevEnabled` | `false` | System One 决策层总开关(实验):`false` = 零行为变化——见上方「Jev 决策层」一节 |
|
|
192
|
+
| `jevBackend` | `zen` | 决策端点:`zen`(免费)/ `native` / `openrouter` |
|
|
193
|
+
| `jevModel` | 空(按 backend:`jev-1.13-free` / `jev-1.13.0` / `typesafe/jev-1.13`) | 版本钉定的决策模型;升级是显式动作 |
|
|
194
|
+
| `jevTimeoutMs` | `3000` | 单次决策请求超时;单发不重试 |
|
|
195
|
+
| `jevSecretFile` | 无 | 出网 secret gate 的外部清单路径;热更换路径即重读 |
|
|
196
|
+
| `jevDebug` | 关 | 诊断日志开关(走宿主 logger,默认静默) |
|
|
129
197
|
|
|
130
198
|
## Acknowledgements
|
|
131
199
|
|
|
@@ -148,7 +216,7 @@ dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory
|
|
|
148
216
|
## 设计文档
|
|
149
217
|
|
|
150
218
|
- [CONTEXT.md](CONTEXT.md) — 领域术语表
|
|
151
|
-
- [docs/adr/](docs/adr/) — 0001–
|
|
219
|
+
- [docs/adr/](docs/adr/) — 0001–0019:OKF 合规、Remote 形态、同步策略、两段式 Observer、Bundle 布局、注入默认值、可观测与调参、双模式持久化、配置向导、子代理隔离、include-subagents 开关、注入去重默认开、更名与迁移、双通道注入、使用加成、决策层直连 HTTP、快道零远程红线、fail-open 硬编码、secret gate 内嵌
|
|
152
220
|
|
|
153
221
|
## License
|
|
154
222
|
|
package/lib/commands.js
CHANGED
|
@@ -11,6 +11,7 @@ import { spawn } from 'node:child_process';
|
|
|
11
11
|
import { serializeTopicDoc, slugify, firstParagraph } from "./okf.js";
|
|
12
12
|
import { buildGraph, renderGraphHtml } from "./viz.js";
|
|
13
13
|
import { CONFIG_KEYS, displayKey, parseConfigValue } from "./config.js";
|
|
14
|
+
import { reloadSecrets } from "./jev/gate.js";
|
|
14
15
|
import { aggregateStats } from "./ilog.js";
|
|
15
16
|
import { askDistillSingle, createOnboardHandler, llmDirectoryNotice, makePanel, parseDistillInput, pickLlmDirectory, } from "./onboard.js";
|
|
16
17
|
export const HELP = [
|
|
@@ -582,5 +583,7 @@ async function doSet(service, tokens, mutate, resolveAsk = () => undefined, reso
|
|
|
582
583
|
await mutate([{ op: 'set', path: [key], value: parsed }]);
|
|
583
584
|
if (key === 'repo' || key === 'autoInject')
|
|
584
585
|
service.invalidate();
|
|
586
|
+
if (key === 'jevSecretFile')
|
|
587
|
+
reloadSecrets(String(parsed)); // hot-swapped list: force re-read
|
|
585
588
|
return ok(`✅ topics.${displayKey(key)} = ${String(parsed)}`);
|
|
586
589
|
}
|
package/lib/config.d.ts
CHANGED
|
@@ -72,6 +72,23 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
72
72
|
usageBoost: z<number, number, "volatile-defined">;
|
|
73
73
|
/** Debounced push delay in GitHub mode. */
|
|
74
74
|
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
75
|
+
/** System One decision-model integration (design 2026-09-25), default-off
|
|
76
|
+
* master switch: false means zero behavior change anywhere (the fast lane
|
|
77
|
+
* never calls out regardless — hard guard); the usage stats stop with it. */
|
|
78
|
+
jevEnabled: z<boolean, boolean, "volatile-defined">;
|
|
79
|
+
/** Decision backend: zen (free, opencode.ai) | native (typesafe
|
|
80
|
+
* first-party) | openrouter (decisions protocol). */
|
|
81
|
+
jevBackend: z<string, string, "volatile-defined">;
|
|
82
|
+
/** Pinned model id; '' = resolve the backend default at call time
|
|
83
|
+
* (distillProvider sentinel pattern): jev-1.13-free / jev-1.13.0 /
|
|
84
|
+
* typesafe/jev-1.13 — an upgrade is a deliberate act. */
|
|
85
|
+
jevModel: z<string, string, "volatile-defined">;
|
|
86
|
+
/** Single-request timeout in ms; one AbortSignal, NO retry (the calling
|
|
87
|
+
* lane's next cadence is the natural retry, fail-open hard-coded). */
|
|
88
|
+
jevTimeoutMs: z<number, number, "volatile-defined">;
|
|
89
|
+
/** External secret list for the outbound secret gate (JEV_SECRET_FILE
|
|
90
|
+
* semantics, design §3.3); swapping the path hot reloads the list. */
|
|
91
|
+
jevSecretFile: z<string, string, "volatile-defined">;
|
|
75
92
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
76
93
|
/** GitHub repo `owner/name`; empty = local-only mode (ADR 0008). */
|
|
77
94
|
repo: z<string, string, "volatile-defined">;
|
|
@@ -139,6 +156,23 @@ export declare const TopicsConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
139
156
|
usageBoost: z<number, number, "volatile-defined">;
|
|
140
157
|
/** Debounced push delay in GitHub mode. */
|
|
141
158
|
pushDebounceSeconds: z<number, number, "volatile-defined">;
|
|
159
|
+
/** System One decision-model integration (design 2026-09-25), default-off
|
|
160
|
+
* master switch: false means zero behavior change anywhere (the fast lane
|
|
161
|
+
* never calls out regardless — hard guard); the usage stats stop with it. */
|
|
162
|
+
jevEnabled: z<boolean, boolean, "volatile-defined">;
|
|
163
|
+
/** Decision backend: zen (free, opencode.ai) | native (typesafe
|
|
164
|
+
* first-party) | openrouter (decisions protocol). */
|
|
165
|
+
jevBackend: z<string, string, "volatile-defined">;
|
|
166
|
+
/** Pinned model id; '' = resolve the backend default at call time
|
|
167
|
+
* (distillProvider sentinel pattern): jev-1.13-free / jev-1.13.0 /
|
|
168
|
+
* typesafe/jev-1.13 — an upgrade is a deliberate act. */
|
|
169
|
+
jevModel: z<string, string, "volatile-defined">;
|
|
170
|
+
/** Single-request timeout in ms; one AbortSignal, NO retry (the calling
|
|
171
|
+
* lane's next cadence is the natural retry, fail-open hard-coded). */
|
|
172
|
+
jevTimeoutMs: z<number, number, "volatile-defined">;
|
|
173
|
+
/** External secret list for the outbound secret gate (JEV_SECRET_FILE
|
|
174
|
+
* semantics, design §3.3); swapping the path hot reloads the list. */
|
|
175
|
+
jevSecretFile: z<string, string, "volatile-defined">;
|
|
142
176
|
}>>, "plain">;
|
|
143
177
|
export type TopicsConfigValue = {
|
|
144
178
|
repo: string;
|
|
@@ -167,8 +201,17 @@ export type TopicsConfigValue = {
|
|
|
167
201
|
deprecatedTtlDays: number;
|
|
168
202
|
usageBoost: number;
|
|
169
203
|
pushDebounceSeconds: number;
|
|
204
|
+
/** System One integration keys (design 2026-09-25 §4). Optional in this TS
|
|
205
|
+
* view only: index.ts's bare-harness DEFAULTS literal predates them and
|
|
206
|
+
* schemastery applies the runtime defaults on real hosts — consumers must
|
|
207
|
+
* treat undefined as the documented default. */
|
|
208
|
+
jevEnabled?: boolean;
|
|
209
|
+
jevBackend?: 'zen' | 'native' | 'openrouter';
|
|
210
|
+
jevModel?: string;
|
|
211
|
+
jevTimeoutMs?: number;
|
|
212
|
+
jevSecretFile?: string;
|
|
170
213
|
};
|
|
171
|
-
export declare const CONFIG_KEYS: readonly ["repo", "autoInject", "injectDedup", "suppressEcho", "topK", "perTopicBudget", "totalBudget", "matchThreshold", "tagBoost", "injectMode", "qualityLane", "graphDepth", "recencyWindowDays", "autoObserve", "includeSubagents", "observationMaxChars", "distillEveryTurns", "distillOnSessionEnd", "distillProvider", "distillModel", "distillBatchSize", "distillMaxModelCalls", "consolidateCadence", "deprecatedTtlDays", "usageBoost", "pushDebounceSeconds"];
|
|
214
|
+
export declare const CONFIG_KEYS: readonly ["repo", "autoInject", "injectDedup", "suppressEcho", "topK", "perTopicBudget", "totalBudget", "matchThreshold", "tagBoost", "injectMode", "qualityLane", "graphDepth", "recencyWindowDays", "autoObserve", "includeSubagents", "observationMaxChars", "distillEveryTurns", "distillOnSessionEnd", "distillProvider", "distillModel", "distillBatchSize", "distillMaxModelCalls", "consolidateCadence", "deprecatedTtlDays", "usageBoost", "pushDebounceSeconds", "jevEnabled", "jevBackend", "jevModel", "jevTimeoutMs", "jevSecretFile"];
|
|
172
215
|
export type ConfigKey = (typeof CONFIG_KEYS)[number];
|
|
173
216
|
/** CamelCase → dash-display (topK → top-k) for command output. */
|
|
174
217
|
export declare function displayKey(key: string): string;
|
package/lib/config.js
CHANGED
|
@@ -72,6 +72,23 @@ export const TopicsConfig = z.object({
|
|
|
72
72
|
usageBoost: z.number().default(0.15).volatile(),
|
|
73
73
|
/** Debounced push delay in GitHub mode. */
|
|
74
74
|
pushDebounceSeconds: z.number().default(45).volatile(),
|
|
75
|
+
/** System One decision-model integration (design 2026-09-25), default-off
|
|
76
|
+
* master switch: false means zero behavior change anywhere (the fast lane
|
|
77
|
+
* never calls out regardless — hard guard); the usage stats stop with it. */
|
|
78
|
+
jevEnabled: z.boolean().default(false).volatile(),
|
|
79
|
+
/** Decision backend: zen (free, opencode.ai) | native (typesafe
|
|
80
|
+
* first-party) | openrouter (decisions protocol). */
|
|
81
|
+
jevBackend: z.string().default('zen').volatile(),
|
|
82
|
+
/** Pinned model id; '' = resolve the backend default at call time
|
|
83
|
+
* (distillProvider sentinel pattern): jev-1.13-free / jev-1.13.0 /
|
|
84
|
+
* typesafe/jev-1.13 — an upgrade is a deliberate act. */
|
|
85
|
+
jevModel: z.string().default('').volatile(),
|
|
86
|
+
/** Single-request timeout in ms; one AbortSignal, NO retry (the calling
|
|
87
|
+
* lane's next cadence is the natural retry, fail-open hard-coded). */
|
|
88
|
+
jevTimeoutMs: z.number().default(3000).volatile(),
|
|
89
|
+
/** External secret list for the outbound secret gate (JEV_SECRET_FILE
|
|
90
|
+
* semantics, design §3.3); swapping the path hot reloads the list. */
|
|
91
|
+
jevSecretFile: z.string().default('').volatile(),
|
|
75
92
|
});
|
|
76
93
|
export const CONFIG_KEYS = [
|
|
77
94
|
'repo',
|
|
@@ -100,6 +117,11 @@ export const CONFIG_KEYS = [
|
|
|
100
117
|
'deprecatedTtlDays',
|
|
101
118
|
'usageBoost',
|
|
102
119
|
'pushDebounceSeconds',
|
|
120
|
+
'jevEnabled',
|
|
121
|
+
'jevBackend',
|
|
122
|
+
'jevModel',
|
|
123
|
+
'jevTimeoutMs',
|
|
124
|
+
'jevSecretFile',
|
|
103
125
|
];
|
|
104
126
|
/** CamelCase → dash-display (topK → top-k) for command output. */
|
|
105
127
|
export function displayKey(key) {
|
|
@@ -119,13 +141,19 @@ export function parseConfigValue(key, raw) {
|
|
|
119
141
|
case 'suppressEcho':
|
|
120
142
|
case 'autoObserve':
|
|
121
143
|
case 'distillOnSessionEnd':
|
|
122
|
-
case 'includeSubagents':
|
|
144
|
+
case 'includeSubagents':
|
|
145
|
+
case 'jevEnabled': {
|
|
123
146
|
if (raw === 'on' || raw === 'true')
|
|
124
147
|
return true;
|
|
125
148
|
if (raw === 'off' || raw === 'false')
|
|
126
149
|
return false;
|
|
127
150
|
return { error: `${key} 取值 on|off` };
|
|
128
151
|
}
|
|
152
|
+
case 'jevBackend': {
|
|
153
|
+
if (raw === 'zen' || raw === 'native' || raw === 'openrouter')
|
|
154
|
+
return raw;
|
|
155
|
+
return { error: 'jev-backend 取值 zen|native|openrouter' };
|
|
156
|
+
}
|
|
129
157
|
case 'topK':
|
|
130
158
|
case 'perTopicBudget':
|
|
131
159
|
case 'totalBudget':
|
|
@@ -136,7 +164,8 @@ export function parseConfigValue(key, raw) {
|
|
|
136
164
|
case 'distillBatchSize':
|
|
137
165
|
case 'distillMaxModelCalls':
|
|
138
166
|
case 'deprecatedTtlDays':
|
|
139
|
-
case 'pushDebounceSeconds':
|
|
167
|
+
case 'pushDebounceSeconds':
|
|
168
|
+
case 'jevTimeoutMs': {
|
|
140
169
|
const n = Number(raw);
|
|
141
170
|
if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n))
|
|
142
171
|
return { error: `${key} 需要非负整数` };
|
|
@@ -167,6 +196,8 @@ export function parseConfigValue(key, raw) {
|
|
|
167
196
|
}
|
|
168
197
|
case 'distillProvider':
|
|
169
198
|
case 'distillModel':
|
|
199
|
+
case 'jevModel':
|
|
200
|
+
case 'jevSecretFile':
|
|
170
201
|
return raw;
|
|
171
202
|
}
|
|
172
203
|
}
|
package/lib/consolidate.d.ts
CHANGED
|
@@ -23,11 +23,19 @@
|
|
|
23
23
|
* - the cadence stamp (meta/consolidate-state.json) advances only when the
|
|
24
24
|
* model actually evaluated at least one cluster — a run that died on the
|
|
25
25
|
* first call leaves the stamp alone, and the next session start retries.
|
|
26
|
+
* - jev prefilter (design 2026-09-25 §2, default-off): with jevEnabled the
|
|
27
|
+
* cluster first answers to ONE cheap System One batch (≤6 questions: the
|
|
28
|
+
* top-5 similar pairs each ask 「同一问题吗」, plus a record-only
|
|
29
|
+
* cluster-level 「值得动吗」). Only when ≥1 pair reaches the adopt band
|
|
30
|
+
* (≥0.50) does the cluster go to the LLM — an all-non-adopt cluster skips
|
|
31
|
+
* the model call entirely. Any jev failure fails open to today's behavior;
|
|
32
|
+
* the merge conclusion generation itself stays LLM.
|
|
26
33
|
*
|
|
27
34
|
* @module consolidate
|
|
28
35
|
*/
|
|
29
36
|
import type { TopicsService } from './service.ts';
|
|
30
37
|
import { type ModelCaller } from './distill.ts';
|
|
38
|
+
import { jevAsk } from './jev/client.ts';
|
|
31
39
|
/** Clusters are capped — a giant cluster drowns the model in candidates. */
|
|
32
40
|
export declare const MAX_CLUSTER_SIZE = 6;
|
|
33
41
|
export interface ConsolidateOp {
|
|
@@ -79,10 +87,21 @@ export interface ClusterEntry {
|
|
|
79
87
|
description?: string;
|
|
80
88
|
conclusion: string;
|
|
81
89
|
}
|
|
90
|
+
/** One similar pair inside a cluster — the jev prefilter's question units. */
|
|
91
|
+
export interface ClusterPair {
|
|
92
|
+
/** Indices into the cluster's `entries`. */
|
|
93
|
+
i: number;
|
|
94
|
+
j: number;
|
|
95
|
+
/** Pairwise Jaccard from clustering — REUSED for the prefilter's pair
|
|
96
|
+
* selection (top-k at ask time), never recomputed. */
|
|
97
|
+
similarity: number;
|
|
98
|
+
}
|
|
82
99
|
export interface TopicCluster {
|
|
83
100
|
entries: ClusterEntry[];
|
|
84
101
|
/** Peak pairwise similarity inside the cluster — runs process peaks first. */
|
|
85
102
|
similarity: number;
|
|
103
|
+
/** Similar pairs (edges ≥ threshold) inside the cluster, best first. */
|
|
104
|
+
pairs: ClusterPair[];
|
|
86
105
|
}
|
|
87
106
|
/**
|
|
88
107
|
* Group entries whose title+description+tags tokens clear the pairwise
|
|
@@ -91,6 +110,9 @@ export interface TopicCluster {
|
|
|
91
110
|
* {@link MAX_CLUSTER_SIZE} keep their most-connected members.
|
|
92
111
|
*/
|
|
93
112
|
export declare function clusterTopics(entries: readonly ClusterEntry[], threshold?: number): TopicCluster[];
|
|
113
|
+
/** The jev seam — injectable so tests run hermetic (same pattern as
|
|
114
|
+
* ModelCaller: production never passes it, the real jevAsk is the default). */
|
|
115
|
+
export type JevAsker = typeof jevAsk;
|
|
94
116
|
/**
|
|
95
117
|
* Drop `deprecated` topics older than `ttlDays` — pure housekeeping, never
|
|
96
118
|
* touches the model. `generated.at` is the right clock: every save restamps
|
|
@@ -105,7 +127,11 @@ export declare class Consolidator {
|
|
|
105
127
|
private inFlight;
|
|
106
128
|
private readonly service;
|
|
107
129
|
private readonly caller;
|
|
108
|
-
|
|
130
|
+
private readonly askJev;
|
|
131
|
+
/** Best-effort host-logger sink (warn-style); undefined = stay silent,
|
|
132
|
+
* aligned with the lane's quiet discipline for success/no-clusters. */
|
|
133
|
+
private readonly note;
|
|
134
|
+
constructor(service: TopicsService, caller: ModelCaller | undefined, askJev?: JevAsker, note?: (message: string) => void);
|
|
109
135
|
get configured(): boolean;
|
|
110
136
|
/** Cadence days for a config value; undefined = off (or unrecognized). */
|
|
111
137
|
static cadenceDays(value: string | undefined): number | undefined;
|
|
@@ -125,6 +151,18 @@ export declare class Consolidator {
|
|
|
125
151
|
private runInner;
|
|
126
152
|
/** Read the active pool and cluster it lexically. */
|
|
127
153
|
private buildClusters;
|
|
154
|
+
/**
|
|
155
|
+
* One jev request per cluster (design 2026-09-25 §2 整理 lane 前置). The
|
|
156
|
+
* top-k similar pairs (k ≤ {@link PREFILTER_TOP_PAIRS}, edges reused from
|
|
157
|
+
* clustering) each get a pair noul question 「同一问题吗」; one record-only
|
|
158
|
+
* cluster-level 「值得动吗」 rides along (固定 band='record', never gates).
|
|
159
|
+
* Gate: ≥1 pair in the adopt band → the cluster goes to the LLM; all pairs
|
|
160
|
+
* below → skipped (省调用). Any jev failure proceeds fail-open — the
|
|
161
|
+
* call-layer telemetry row (outcome !== 'ok' marks the fail-open) was already written by jevAsk
|
|
162
|
+
* itself; only the verdict layer is ours here (agree='n/a', nothing lexical
|
|
163
|
+
* to reconcile against on this lane).
|
|
164
|
+
*/
|
|
165
|
+
private prefilterCluster;
|
|
128
166
|
/** One model call over one cluster, ops shape-validated but not yet applied. */
|
|
129
167
|
private runCluster;
|
|
130
168
|
/**
|