instar 1.3.753 → 1.3.754
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/dist/commands/server.d.ts +30 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +116 -16
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +20 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/componentCategories.d.ts.map +1 -1
- package/dist/core/componentCategories.js +6 -0
- package/dist/core/componentCategories.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/data/llmBenchCoverage.d.ts.map +1 -1
- package/dist/data/llmBenchCoverage.js +6 -0
- package/dist/data/llmBenchCoverage.js.map +1 -1
- package/dist/threadline/HubIntentClassifier.d.ts +150 -0
- package/dist/threadline/HubIntentClassifier.d.ts.map +1 -0
- package/dist/threadline/HubIntentClassifier.js +322 -0
- package/dist/threadline/HubIntentClassifier.js.map +1 -0
- package/dist/threadline/hubCommands.d.ts +16 -12
- package/dist/threadline/hubCommands.d.ts.map +1 -1
- package/dist/threadline/hubCommands.js +16 -30
- package/dist/threadline/hubCommands.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/llmBenchCoverage.ts +7 -0
- package/upgrades/1.3.754.md +46 -0
- package/upgrades/side-effects/keyword-intent-conversion-3-hubcommands.md +170 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Side-Effects Review — Hub-intent recognizer: regex → LLM-with-context (Conversion #3)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `keyword-intent-conversion-3-hubcommands`
|
|
4
|
+
**Date:** `2026-07-03`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `subagent (fresh reviewer) — see appended verdict`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Replaces the keyword/regex DECISION in `src/threadline/hubCommands.ts` `parseHubCommand()` (anchored
|
|
11
|
+
whole-message regexes for "open this" / "tie this to <topic>") with an LLM-with-context classifier,
|
|
12
|
+
`src/threadline/HubIntentClassifier.ts`. The recognizer runs at the `telegram.onTopicMessage` hub
|
|
13
|
+
intercept in `src/commands/server.ts`, where a positive decision SWALLOWS the message before the agent
|
|
14
|
+
sees it and performs a bind — so a misread silently EATS a real message. The new classifier infers
|
|
15
|
+
open/tie intent from the message + a bounded recent-conversation window, constrains a `tie` target to a
|
|
16
|
+
structured enum of real existing topics (validated in code by numeric id membership, never prose-match),
|
|
17
|
+
and FAILS OPEN on every uncertainty (message passes through, never swallowed). The authoritative binder
|
|
18
|
+
`bindHubConversation` is unchanged. Ships dev-gated dark (config `threadline.hubIntent`, `enabled` omitted
|
|
19
|
+
→ `resolveDevAgentGate`) + dry-run first (`logs/hub-intent.jsonl`). Files: `HubIntentClassifier.ts` (new),
|
|
20
|
+
`hubCommands.ts` (regex removed), `server.ts` (wiring + `resolveHubClassifierDeps`), `ConfigDefaults.ts`,
|
|
21
|
+
`devGatedFeatures.ts`, `componentCategories.ts`, `LLM-ROUTING-REGISTRY.md`, three test tiers + updated
|
|
22
|
+
`hubCommands.test.ts` + recomputed dark-gate line-map. Follows the proven `MoveIntentClassifier` exemplar
|
|
23
|
+
(PR #1367).
|
|
24
|
+
|
|
25
|
+
## Decision-point inventory
|
|
26
|
+
|
|
27
|
+
- `hubCommands.parseHubCommand` (`src/threadline/hubCommands.ts`) — **remove** — the anchored-regex "is
|
|
28
|
+
this a hub bind command?" decision is deleted.
|
|
29
|
+
- `HubIntentClassifier.classifyHubIntent` (`src/threadline/HubIntentClassifier.ts`) — **add** — the LLM
|
|
30
|
+
decision that replaces it (open/tie/null + enum-validated target + confidence).
|
|
31
|
+
- `onTopicMessage` hub intercept (`src/commands/server.ts`) — **modify** — swaps regex call for classifier
|
|
32
|
+
+ dev-gate + dry-run gate + audit; still returns (swallows) only on a real, non-dry-run command.
|
|
33
|
+
- `bindHubConversation` (`src/threadline/hubCommands.ts`) — **pass-through** — unchanged binder.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Over-block
|
|
38
|
+
|
|
39
|
+
Over-block here = wrongly SWALLOWING a legitimate message the user did not mean as a bind command (the
|
|
40
|
+
exact harm the old regex caused, e.g. "should I open this?" / "open this in a new tab"). The new decision
|
|
41
|
+
is LLM-with-context, so these are correctly classified as discussion (covered by the discrimination
|
|
42
|
+
corpus). Structural protections against over-block: (a) FAIL-OPEN — any uncertainty passes through;
|
|
43
|
+
(b) a high confidence bar (0.85) to swallow; (c) a `tie` whose target isn't a real topic id passes
|
|
44
|
+
through; (d) the whole path is DARK on the fleet and DRY-RUN on dev, so nothing is swallowed until a
|
|
45
|
+
deliberate `dryRun:false` gated on a live accuracy benchmark. Net: over-block strictly decreases vs the
|
|
46
|
+
shipped regex.
|
|
47
|
+
|
|
48
|
+
## 2. Under-block
|
|
49
|
+
|
|
50
|
+
Under-block = failing to recognize a genuine bind command (message reaches the agent instead of
|
|
51
|
+
auto-binding). This is the SAFE direction here (a missed auto-bind costs a restate; the agent can still
|
|
52
|
+
bind via the API). Sources: (a) the cheap pre-filter skips messages with no bind-ish stem word, so a
|
|
53
|
+
paraphrase like "put this under the roadmap topic" is not auto-bound; (b) fail-open passes through on
|
|
54
|
+
provider failure; (c) while dark/dry-run nothing is bound. All acceptable — the design deliberately trades
|
|
55
|
+
missed auto-binds for never eating a message.
|
|
56
|
+
|
|
57
|
+
## 3. Level-of-abstraction fit
|
|
58
|
+
|
|
59
|
+
Correct layer: an AUTHORITY (context-rich LLM reasoning), not a brittle detector. It replaces a brittle
|
|
60
|
+
detector (regex) that wrongly held swallow authority. It routes through the shared `IntelligenceProvider`
|
|
61
|
+
(the same smart-gate substrate `CoherenceGate` uses) rather than re-implementing an LLM path, and reuses
|
|
62
|
+
the existing `bindHubConversation` binder rather than duplicating bind logic. The cheap pre-filter is a
|
|
63
|
+
low-level primitive used ONLY to drop toward pass-through (never to decide a positive), exactly as the
|
|
64
|
+
standard permits.
|
|
65
|
+
|
|
66
|
+
## 4. Signal vs authority compliance
|
|
67
|
+
|
|
68
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
69
|
+
|
|
70
|
+
- [x] Yes — the logic is a smart gate with full conversational context (LLM-backed with recent history).
|
|
71
|
+
|
|
72
|
+
The classifier holds the swallow decision, but it is an LLM reasoning over the message + a bounded recent
|
|
73
|
+
conversation window (not brittle logic), and it fails open. The only string-matching in the module is the
|
|
74
|
+
pre-filter, which can never DECIDE a positive command — it only drops obvious non-commands toward
|
|
75
|
+
pass-through. Enum validation of the model's emitted `targetTopicId` is numeric-id membership, not
|
|
76
|
+
prose-matching.
|
|
77
|
+
|
|
78
|
+
## 5. Interactions
|
|
79
|
+
|
|
80
|
+
- **Shadowing:** the hub intercept runs inside `onTopicMessage` before normal session routing, exactly
|
|
81
|
+
where the regex ran. When dark/dry-run OR fail-open OR not-a-command, it returns `handled:false`
|
|
82
|
+
(falls through) so downstream routing (fix-commands, session dispatch) is unaffected — it can only
|
|
83
|
+
shadow routing when it genuinely swallows a real command, which was the old behavior too.
|
|
84
|
+
- **Double-fire:** only one intercept owns hub-command recognition; there is no second recognizer to
|
|
85
|
+
double-fire with. The `POST /threadline/hub/bind` API route is a separate, explicit caller (no text
|
|
86
|
+
decision) and is unchanged.
|
|
87
|
+
- **Races:** none new — the classifier is stateless; the audit append is best-effort and wrapped so it
|
|
88
|
+
never gates the message. `bindHubConversation`'s existing CAS mutate is unchanged.
|
|
89
|
+
- **Feedback loops:** none — the classifier reads recent history but does not write to it.
|
|
90
|
+
|
|
91
|
+
## 6. External surfaces
|
|
92
|
+
|
|
93
|
+
- **Other agents / users:** none while dark on the fleet. On a dev agent (dry-run) the only new surface is
|
|
94
|
+
the machine-local `logs/hub-intent.jsonl` (80-char scrubbed preview, LLM-engaged decisions only).
|
|
95
|
+
- **External systems:** one bounded fast-tier LLM call per candidate hub message through the shared
|
|
96
|
+
provider (spawn-cap + breaker), attributed `HubIntentClassifier`. No new network egress beyond that.
|
|
97
|
+
- **Persistent state:** none beyond the append-only dry-run log.
|
|
98
|
+
- **Operator surface (Mobile-Complete):** no operator-facing actions added — the only control is a config
|
|
99
|
+
flag (`threadline.hubIntent.dryRun/enabled`), same as every dev-gated feature. Not applicable.
|
|
100
|
+
|
|
101
|
+
## 6b. Operator-surface quality
|
|
102
|
+
|
|
103
|
+
No operator surface — not applicable. This change touches no dashboard renderer, approval page, or
|
|
104
|
+
grant/revoke/secret form.
|
|
105
|
+
|
|
106
|
+
## 7. Multi-machine posture
|
|
107
|
+
|
|
108
|
+
**machine-local BY DESIGN.** The hub intercept and its dry-run log are per-machine inbound-message
|
|
109
|
+
processing on whichever machine owns the hub conversation; there is no cross-machine state to replicate
|
|
110
|
+
and no URL generated. It emits NO user-facing notices (it either swallows a command and lets the existing
|
|
111
|
+
binder post the hub confirmation, or passes through), so no one-voice gating is needed. It holds no durable
|
|
112
|
+
state that could strand on topic transfer (the audit log is local observability). The config flag resolves
|
|
113
|
+
per-machine via `resolveDevAgentGate`, consistent with every other dev-gated feature; on the fleet it is
|
|
114
|
+
uniformly dark.
|
|
115
|
+
|
|
116
|
+
## 8. Rollback cost
|
|
117
|
+
|
|
118
|
+
Pure code change — revert and ship a patch. No persistent state needing cleanup (the dry-run log is
|
|
119
|
+
append-only machine-local observability that can be ignored/deleted). No agent-state repair, no
|
|
120
|
+
user-visible regression during the rollback window (the feature is dark on the fleet, so a revert is a
|
|
121
|
+
no-op for fleet agents). The rollback lever short of a revert is `threadline.hubIntent` staying
|
|
122
|
+
dark/dry-run, or `enabled:false` to force-dark even a dev agent.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Second-pass review (Phase 5)
|
|
127
|
+
|
|
128
|
+
**Reviewer:** fresh subagent (independent audit). **Verdict: Concur with the review.**
|
|
129
|
+
|
|
130
|
+
The reviewer independently traced every `return` in `classifyHubIntent` and confirmed only two paths
|
|
131
|
+
yield `isCommand:true` (a high-confidence `open`, or a high-confidence `tie` with an enum-resolved
|
|
132
|
+
target), both behind the confidence gate; every other path (empty text, no bind signal, no provider,
|
|
133
|
+
throw/timeout, unparseable/schema-violation, intent null, below confidence, tie-target-not-in-enum)
|
|
134
|
+
returns pass-through. The wiring's `willAct = isCommand && !dryRun` gate means the message is swallowed
|
|
135
|
+
ONLY on a real command with a resolved target and `dryRun:false`; the dark gate (`if clsDeps?.enabled`)
|
|
136
|
+
and dry-run default both skip swallowing; the audit write is wrapped so it can never break the path, and
|
|
137
|
+
the outer try/catch fails open to normal routing. Enum validation is numeric-id membership (never
|
|
138
|
+
prose-match); the pre-filter can only drop toward pass-through. The classifier never throws.
|
|
139
|
+
|
|
140
|
+
**One non-blocking observation:** while dark on the fleet, the "open this"/"tie this" structural
|
|
141
|
+
interception no longer fires (no regex fallback), so those messages reach the agent — whereas the fleet
|
|
142
|
+
CLAUDE.md still says the agent will NOT see "open this." This is the intentional dark-rollout tradeoff
|
|
143
|
+
already recorded in §2 (Under-block) / §5 (Shadowing); it is the SAFE direction (pass-through, never
|
|
144
|
+
swallow) and not a safety concern. It is a product-completeness item to close at graduation (flip the
|
|
145
|
+
gate, or update the fleet CLAUDE.md guidance for the dark window) — not a blocker for this change.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
**CI follow-up (2026-07-04):** the new `HubIntentClassifier` gate is registered in
|
|
150
|
+
`COMPONENT_CATEGORY`, which the untrusted-input-classification ratchet requires to carry an explicit
|
|
151
|
+
`LLM_UNTRUSTED_INPUT` classification. It judges an inbound hub message's bind-intent (untrusted user
|
|
152
|
+
text), so it is classified `true` in `src/data/llmBenchCoverage.ts`. Mechanical consequence of the
|
|
153
|
+
component registration; no behavior change.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Post-rebase addendum (main now carries #1367)
|
|
158
|
+
|
|
159
|
+
After the move-intent exemplar (PR #1367) merged, `main` gained the keyword-intent classification-ratchet family. Rebasing this branch onto it required registering `HubIntentClassifier` (a new `COMPONENT_CATEGORY` key) across every classification map, exactly mirroring how #1367 registered `MoveIntentClassifier`:
|
|
160
|
+
|
|
161
|
+
- `src/data/llmBenchCoverage.ts`:
|
|
162
|
+
- `LLM_BENCH_COVERAGE`: `{ exempt }` — ships its own discrimination benchmark (`tests/unit/hub-intent-discrimination.test.ts` + opt-in `INSTAR_LIVE_HUB_INTENT=1`), the co-located benchmark IS the benchmark (same argument as MoveIntentClassifier / InteractivePoolCanaryJudge).
|
|
163
|
+
- `LLM_JUDGES_CLAIMS`: bare `false` — classifies a USER's bind-intent, not an agent/session completion/health/credit claim.
|
|
164
|
+
- `LLM_PARSER_CONTRACT`: `{ pending: 'contract-wave-2' }` — parses a closed intent(open/tie/null) + targetTopicId-enum + confidence verdict.
|
|
165
|
+
- `LLM_UNTRUSTED_INPUT`: `true` — judges untrusted inbound hub text (landed in the prior fix commit).
|
|
166
|
+
- Pinned shrink-only baselines updated (each a visible, reviewed act): `EXEMPT_BASELINE` (`tests/unit/llm-bench-coverage-ratchet.test.ts`) and `PENDING_BASELINE` (`tests/unit/parser-contract-classification-ratchet.test.ts`) each gain `HubIntentClassifier`.
|
|
167
|
+
- `tests/unit/keyword-intent-decision-ratchet.test.ts`: `threadline/hubCommands.ts` removed from `EXPECTED_OFFENDERS` and `BASELINE` dropped 5→4 (the converted file no longer keyword-decides intent; the detector confirms exactly 4 remaining offenders). `topicProfileIngress` #1 remains for its own conversion.
|
|
168
|
+
- Merge conflicts in `componentCategories.ts`, `devGatedFeatures.ts`, `LLM-ROUTING-REGISTRY.md`, and `lint-dev-agent-dark-gate.test.ts` resolved keeping BOTH #1367's and this change's registrations; the dark-gate line-map recomputed against the merged `ConfigDefaults` (hubIntent +20 lines and moveIntent +18 lines both present, neither adds an attributed path).
|
|
169
|
+
|
|
170
|
+
No behavior change from this addendum — all registrations are ratchet metadata / test baselines. The classifier, wiring, config, and fail-open contract are unchanged from the reviewed version above.
|