@lmzhen/dsh-evolution-review 0.32.3 → 0.32.5
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 +11 -52
- package/lib/index.js +15 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,64 +1,23 @@
|
|
|
1
1
|
# @lmzhen/dsh-evolution-review
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
and completion channels), builds the review request, and executes the writes the validated
|
|
5
|
-
plan calls for at conversation end. It registers no prompt section and no tool schema of its
|
|
6
|
-
own, and the delivery mode lives on the `evolution-policy` row, not here.
|
|
3
|
+
> 后台复盘:在自然节点回顾对话并给改进建议。
|
|
7
4
|
|
|
8
|
-
##
|
|
5
|
+
## 你会看到
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
- **Prompt prefix / KV cache:** independent of request-prefix construction — it does not alter the assembled prompt or tool list; family-level rules: `packages/README.md` §"Model-visible prompt prefix and the KV cache".
|
|
12
|
-
- **Mount it?** yes — the `evolution-review` row, carried by the `evolution-host`, `evolution-all` and one-click `evolution-preset` bundles (the agent-preset delta carries only the four model rows).
|
|
7
|
+
偶尔出现在会话里的复盘提示;不是每句话都跑。
|
|
13
8
|
|
|
14
|
-
##
|
|
9
|
+
## 什么时候会用到
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
- The review runs in the parent session in the default `reviewMode: 'inject'`; the parent's model and cached prefix are reused.
|
|
18
|
-
- Read-before-write (the rule is `evolution-core`'s `skill-reads.ts`, shared with the tool path's admission gate) tracks only reads through the `skill` tool. `skill_manage` has no per-skill read action (`list`/`review` are whole-library, not targeted at one name), so a skill that was only listed via `skill_manage` is not marked as read: a background review may still reject a patch to it until it is actually loaded.
|
|
19
|
-
- The completion-channel counters (`cumulativeToolCalls` / `completionInjected`) are in-memory only. A process restart resets them, which is accepted behavior: the completion review is a one-per-session post-task adaptation and a restart is treated as a fresh conversation boundary. The cadence state (`turnsSinceMemory` / `turnsSinceSkill`) is persisted via `ReviewState` and survives restart: bounded by `REVIEW_STATE_SESSION_CAP` (500, seam constant): the least-recently-active sessions are pruned on save, so a very old session restarting resumes from a fresh cadence baseline rather than an unbounded store.
|
|
20
|
-
- `evolution/review-scheduled` and `evolution/review-error` are emitted for platform/user wiring only: this family has no in-repo production `ctx.on` consumer for them. They are declared externally owned (the platform side wires consumption), which matches the `EXEMPT_ORPHANS` set in `scripts/verify-event-pairing.mjs`.
|
|
21
|
-
- When the `evolution-state` service is not mounted, the memory/skill cadence state is not persisted and every turn restarts from a clean `{ turnsSinceMemory: 0, turnsSinceSkill: 0 }` baseline: the review schedule is stateless and re-decided each turn rather than accumulating across the conversation. The loss is surfaced once per process as a logger warning at the first turn/end.
|
|
22
|
-
- **Hosts without `agent.inbox` fall back to delivery-time settlement.** With no queue to observe there is no `claimed`/`discarded` to wait for, so the delivery itself closes the window (`delivery-no-inbox`) and each completed boundary may deliver again: that is the pre-0.3.41 behavior, and under `interval=1` it can chain review turns. Every current host exposes `Agent.inbox`, so this is the degradation path for stub or older agents (the plugin's own tests rely on it).
|
|
23
|
-
- **The default `reviewMode: 'inject'` produces no plan ledger.** The review runs in the parent session and emits no `evolution/plan-applied` event. `reviewMode: 'branch'` is a temporary platform-gap path: it keeps the audited child path, but the platform cannot yet make that child durable-log-governable, parent-prefix-cache-reusing, and workspace-clean at the same time. See "Branch review is a temporary platform-gap path".
|
|
24
|
-
- **Evidence that cites only bookkeeping frames is REPORTED, never refused (phase 1).** `EVIDENCE_CLASS` (`evolution-plan-validator`) counts the ops whose entire `evidence` list points at turn/step boundary frames — a seq the range rule accepts but that carries no content — and this plugin logs one `dsh-evolution-review:` warning per plan; the count rides `evolution/plan-applied` as `evidenceClassReports` and appears in no model-visible text. Nothing is dropped: `EVIDENCE_RANGE` remains the only evidence gate, and a session log whose frames carry no `seq` leaves the report silent (`undefined` is not an empty index) instead of reporting every op.
|
|
25
|
-
- **`.pinned` protection in the `'inject'` channel rides a family-internal session mark.** The review prompt marks the parent session, `tool-skill-manage` reads that mark and resolves both origin surfaces (approval + library) to `background_review`, and the next REAL user message (`source.kind === 'user'`) clears it (plugin notices, including the review prompt itself, do not). Two bounds follow from the platform's inject contract (no driver wake; a prompt is dropped on cancel/dispose and may be missed with an already-claimed batch): with `reviewWakeInject: false`, or on a host without `followup`, (a) a pending prompt may never execute while the session stays idle, and (b) when the user's next message wakes the session, the mark is cleared before that pending prompt reaches the model (its writes are then attributed `foreground` and the pinned guard does not cover them). A prompt delivered while a HUMAN message is already queued is not marked at all: the platform claims that human turn first, so a session-level window would attribute the user's own writes to `background_review` (S2-8, FLOW1-3): the plugin reads the pre-claim queue (`agent.inbox`) because the platform exposes no claim identity, and warns once per mount when it withholds the mark.
|
|
11
|
+
Agent 干完一段活、达到节奏时。
|
|
26
12
|
|
|
27
|
-
##
|
|
13
|
+
## 默认 / 前提
|
|
28
14
|
|
|
29
|
-
|
|
15
|
+
默认在会话内、按 cadence 触发。
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
2. reuse of the parent session's already-cached prefix (no full re-prefill);
|
|
33
|
-
3. no pollution of the user's workspace with a transient child session.
|
|
17
|
+
## 边界
|
|
34
18
|
|
|
35
|
-
|
|
19
|
+
子会话模式需要 agents;当前有已知通知边界。
|
|
36
20
|
|
|
37
|
-
##
|
|
21
|
+
## 维护者参考:参数别名(G0/S0.3)
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
- `reviewMode`: `'inject'` (default) or `'branch'` (temporary platform-gap path; see known limitations).
|
|
41
|
-
- `reviewWakeInject` (default `true`): waking (`agent.followup`) delivery instead of the non-waking `agent.inject`.
|
|
42
|
-
- `skillReviewTrigger` (`'cadence'` default, or `'completion'` / `'both'`): gates only the completion channel.
|
|
43
|
-
- Deprecated names (G0/S0.3): `memoryInterval` and `skillInterval` are the legacy
|
|
44
|
-
spellings of the policy row's `reviewMemoryInterval` / `reviewSkillInterval`
|
|
45
|
-
(the snapshot shadows them in every shipped composition). Reading them still
|
|
46
|
-
works; writing them is refused. Removal was planned for 0.7.0 and is deferred: both aliases still ship, so no later version is claimed here.
|
|
47
|
-
|
|
48
|
-
### Review delivery contract
|
|
49
|
-
|
|
50
|
-
- **Both channels execute at conversation end only** (a `turn/end` with `reason.kind === 'completed'`): a cadence threshold fire mid-task merely latches the kind. `reviewMode` selects how the flush delivers: `'inject'` (default) or the temporary `'branch'` path.
|
|
51
|
-
- Rationale for `inject`: the parent session already holds a warm prefix cache, so an injected prompt costs only the new tokens. `branch` exists only because the platform lacks a child that can reuse the parent prefix while remaining durable-log-governable and workspace-clean; it is not the default and carries the documented residue risk.
|
|
52
|
-
- **`skillReviewTrigger`** (default `'cadence'`): the cadence channel is **always on** (one end-of-conversation review from the cadence latch per task segment); the flag gates **only the completion channel**: `'cadence'` disables it, `'completion'` enables it (cadence still fires), `'both'` enables it on top of the always-on cadence. At one boundary a turn is served by exactly one review: the cadence flush runs first and returns, so `'both'` never double-sends a second task-complete prompt at the same boundary (V10-13).
|
|
53
|
-
- **`reviewWakeInject`** (default `true`): deliveries use `agent.followup` (next-turn + wake: the model starts processing immediately) instead of the non-waking `agent.inject` (which waits for the next driver wake). The host falls back to `inject` when it has no followup or the option is `false`. **The wake primitive is always called ON the agent instance:** the platform's `Agent.followup`/`inject` are prototype methods that call `this.send(...)`, so extracting one into a local and calling the detached reference throws (0.3.73: that throw was caught and logged while the cadence reset still ran, silently consuming every segment's review from 2026-09-07). A refused delivery now returns `false` and the caller keeps its latch and counters, so the review retries at the next completed boundary instead of vanishing; rule N13b in `packages/scripts/verify-arch-guards.mjs` pins the call form mechanically (comments and string literals are masked, and the detector self-tests at startup). The turn that runs our notice is excluded from the counting window (an injected review prompt alone must not re-trigger a review under `interval=1`): the platform reports which turn claimed the notice (`agent/inbox/claimed` carries the turn), that turn's `turn/end` settles the window and that turn is not counted, and any unrelated turn ending in the meantime is unaffected (V7-02, S2-9, FLOW1-5).
|
|
54
|
-
- **In-flight triggers coalesce, and the drain settles at delivery**: a trigger that arrives while a review is in flight is queued as ONE entry per (session, kind): last trigger wins, so one window can never deliver two prompts for the same segment, and the next completed boundary starts a fresh run once the window closes (the settle wait is bounded: the RESULT watchdog's budget runs from the abort deadline (the full `reviewTimeoutMs` PLUS the 5 s settle margin, the sum capped at the platform timer ceiling), while the `run.dispose` arm point counts that margin alone, from the moment the result settled (PLAN S1.1 / P2-1 / P2-2, 0.3.83); a handle the platform never settles after its abort is abandoned and reported as `evolution/review-error`). A drain delivery that fails restores that session's cadence latch (and, on the completion channel, its `completionInjected` flag), so the segment's review retries at the next completed boundary instead of vanishing with no trace (S2-7, FLOW1-2/1-4).
|
|
55
|
-
- **One review at a time: the counting window follows the delivered notice, not the delivery.** The `turnsSinceMemory`/`turnsSinceSkill` counters stay monotonic across threshold fires (`resetOnFire: false`) and restart when the notice SETTLES: the queue dropped it unrun (removed, cleared, replaced — `agent/inbox/discarded`) or the turn that claimed it (`agent/inbox/claimed` names the turn) ended. While a notice is outstanding — still queued, or claimed and running — `deliverMessage` refuses to queue another review prompt (it answers `false`, so the caller keeps its one-shot latch and retries at a later boundary); that is what keeps a long task from stacking prompt after prompt. Result notices are not gated. The record is in-memory while the queue is durable, so the judgement reads both: a queue still holding one of our notices with no record (a restart) keeps the window shut, and a record whose notice has left the queue settles as `queue-lost`, while a missed `turn/end` for the claiming turn settles at the next turn that does end (turn numbers increase per session) — a lost event must not wedge the gate. Every settle logs its attribution (`discarded` / `turn-end` / `delivery-no-inbox` / `queue-lost`). A threshold fire on the completing turn is caught by the flush (`pendingKind = latch ?? kind`). All deliveries (review prompt AND result notices) share the same waking channel; a failed counter-reset persist warns once per session (a stateful reload may re-deliver).
|
|
56
|
-
|
|
57
|
-
**Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
|
|
58
|
-
|
|
59
|
-
## Notes and history
|
|
60
|
-
|
|
61
|
-
- The mark is in-memory, so a restart drops the window (v37 S2.2, plan decision (b)).
|
|
62
|
-
- The former `executionTimeoutMs` declaration was removed in v14 (nothing read it, so it was configuration that did nothing); use `reviewTimeoutMs`.
|
|
63
|
-
- The explicit `'inject'` mode's historical "immediate on threshold" contract was superseded in 0.3.39: both modes are end-of-conversation.
|
|
64
|
-
- Review delivery contract (0.3.38-0.3.42)
|
|
23
|
+
`memoryInterval` → `reviewMemoryInterval`;`skillInterval` → `reviewSkillInterval`。旧名仍可读,写入会拒绝。
|
package/lib/index.js
CHANGED
|
@@ -618,10 +618,16 @@ function apply(ctx, rawConfig = {}) {
|
|
|
618
618
|
const startedAt = Date.now();
|
|
619
619
|
const controller = new AbortController();
|
|
620
620
|
let timedOut = false;
|
|
621
|
+
const timeoutDone = Promise.withResolvers();
|
|
621
622
|
const timer = setTimeout(() => {
|
|
622
623
|
timedOut = true;
|
|
623
624
|
controller.abort();
|
|
624
|
-
|
|
625
|
+
timeoutDone.resolve();
|
|
626
|
+
handle?.agent.cancel({
|
|
627
|
+
kind: "hook",
|
|
628
|
+
reason: "evolution-review-timeout"
|
|
629
|
+
});
|
|
630
|
+
}, config.reviewTimeoutMs);
|
|
625
631
|
let handle;
|
|
626
632
|
try {
|
|
627
633
|
const presets = ctx.get("agentPresets");
|
|
@@ -697,7 +703,14 @@ function apply(ctx, rawConfig = {}) {
|
|
|
697
703
|
}],
|
|
698
704
|
source: { kind: "user" }
|
|
699
705
|
}));
|
|
700
|
-
|
|
706
|
+
const idle = handle.agent.whenIdle().catch((error) => {
|
|
707
|
+
if (collected.error === void 0) collected.error = error instanceof Error ? error.message : String(error);
|
|
708
|
+
});
|
|
709
|
+
await Promise.race([idle, timeoutDone.promise]);
|
|
710
|
+
if (timedOut) handle.agent.cancel({
|
|
711
|
+
kind: "hook",
|
|
712
|
+
reason: "evolution-review-timeout"
|
|
713
|
+
});
|
|
701
714
|
offEvent();
|
|
702
715
|
if (collected.finalText.trim() !== "") deliverMessage(agent, collected.finalText, "branch review result", false);
|
|
703
716
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-review",
|
|
3
3
|
"description": "Background review orchestration (community build)",
|
|
4
|
-
"version": "0.32.
|
|
4
|
+
"version": "0.32.5",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.32.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.32.5"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
36
36
|
"@deepseek-ai/dsh-session": "^0.2.1-rc.2",
|
|
37
37
|
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
38
|
-
"@lmzhen/dsh-evolution-policy": "^0.32.
|
|
39
|
-
"@lmzhen/dsh-evolution-state": "^0.32.
|
|
38
|
+
"@lmzhen/dsh-evolution-policy": "^0.32.5",
|
|
39
|
+
"@lmzhen/dsh-evolution-state": "^0.32.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@deepseek-ai/dsh-agent": "^0.2.1-rc.2",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
45
45
|
"@deepseek-ai/dsh-session": "^0.2.1-rc.2",
|
|
46
46
|
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
47
|
-
"@lmzhen/dsh-evolution-core": "^0.32.
|
|
48
|
-
"@lmzhen/dsh-evolution-state": "^0.32.
|
|
47
|
+
"@lmzhen/dsh-evolution-core": "^0.32.5",
|
|
48
|
+
"@lmzhen/dsh-evolution-state": "^0.32.5"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"dsh": ">=0.2.1-rc.2 <0.2.2-0"
|