instar 1.3.508 → 1.3.510
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.map +1 -1
- package/dist/commands/server.js +156 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutonomousSessions.d.ts +8 -0
- package/dist/core/AutonomousSessions.d.ts.map +1 -1
- package/dist/core/AutonomousSessions.js +34 -0
- package/dist/core/AutonomousSessions.js.map +1 -1
- package/dist/core/MeshRpc.d.ts +6 -1
- package/dist/core/MeshRpc.d.ts.map +1 -1
- package/dist/core/MeshRpc.js +7 -0
- package/dist/core/MeshRpc.js.map +1 -1
- package/dist/core/OwnershipReconciler.d.ts.map +1 -1
- package/dist/core/OwnershipReconciler.js +17 -0
- package/dist/core/OwnershipReconciler.js.map +1 -1
- package/dist/core/SessionDrainRunner.d.ts +116 -0
- package/dist/core/SessionDrainRunner.d.ts.map +1 -0
- package/dist/core/SessionDrainRunner.js +162 -0
- package/dist/core/SessionDrainRunner.js.map +1 -0
- package/dist/core/SessionManager.d.ts +4 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +3 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionOwnership.d.ts +22 -1
- package/dist/core/SessionOwnership.d.ts.map +1 -1
- package/dist/core/SessionOwnership.js +17 -1
- package/dist/core/SessionOwnership.js.map +1 -1
- package/dist/core/TopicProfileOrchestrator.d.ts +3 -1
- package/dist/core/TopicProfileOrchestrator.d.ts.map +1 -1
- package/dist/core/TopicProfileOrchestrator.js +8 -3
- package/dist/core/TopicProfileOrchestrator.js.map +1 -1
- package/dist/core/TopicProfileResolver.d.ts +9 -1
- package/dist/core/TopicProfileResolver.d.ts.map +1 -1
- package/dist/core/TopicProfileResolver.js +38 -2
- package/dist/core/TopicProfileResolver.js.map +1 -1
- package/dist/core/TopicProfileStore.d.ts +8 -1
- package/dist/core/TopicProfileStore.d.ts.map +1 -1
- package/dist/core/TopicProfileStore.js +2 -2
- package/dist/core/TopicProfileStore.js.map +1 -1
- package/dist/core/classifyProfileChange.d.ts.map +1 -1
- package/dist/core/classifyProfileChange.js +32 -1
- package/dist/core/classifyProfileChange.js.map +1 -1
- package/dist/core/frameworkSessionLaunch.d.ts +28 -1
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +35 -3
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/core/seamlessnessConfig.d.ts +8 -1
- package/dist/core/seamlessnessConfig.d.ts.map +1 -1
- package/dist/core/seamlessnessConfig.js +8 -1
- package/dist/core/seamlessnessConfig.js.map +1 -1
- package/dist/core/topicProfileIngress.d.ts.map +1 -1
- package/dist/core/topicProfileIngress.js +14 -0
- package/dist/core/topicProfileIngress.js.map +1 -1
- package/dist/core/topicProfileValidation.d.ts +12 -0
- package/dist/core/topicProfileValidation.d.ts.map +1 -1
- package/dist/core/topicProfileValidation.js +30 -0
- package/dist/core/topicProfileValidation.js.map +1 -1
- package/dist/core/topicProfileWriteSurface.d.ts.map +1 -1
- package/dist/core/topicProfileWriteSurface.js +3 -2
- package/dist/core/topicProfileWriteSurface.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/middleware.d.ts +14 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +17 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts +11 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +50 -3
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/1.3.509.md +74 -0
- package/upgrades/1.3.510.md +54 -0
- package/upgrades/side-effects/multi-machine-seamlessness-ws12b-drain.md +205 -0
- package/upgrades/side-effects/topic-effort-pin.md +117 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Side-Effects Review — WS1.2b drain verb: active-topic transfers COMPLETE
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `multi-machine-seamlessness-ws12b-drain`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent (MANDATORY — ownership + session lifecycle; verdict appended below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Implements the WS1.2 drain semantics from the merged MULTI-MACHINE-SEAMLESSNESS-SPEC
|
|
11
|
+
(§WS1.2): a transfer of an ACTIVELY-USED conversation now COMPLETES — the owner
|
|
12
|
+
finishes the in-flight turn (bounded), suspends any autonomous run (the remote WS1.4
|
|
13
|
+
arm), closes its local session, and lands the target's claim, which is exactly the
|
|
14
|
+
moment the durable queue's ownership-contention barrier releases inbound to the new
|
|
15
|
+
owner. Files: `src/core/SessionOwnership.ts` (FSM `abort-transfer` action),
|
|
16
|
+
`src/core/SessionDrainRunner.ts` (new owner-side bounded sequence, all I/O injected),
|
|
17
|
+
`src/core/MeshRpc.ts` (`drain` verb + router-only RBAC + `drain-unauthorized` 403),
|
|
18
|
+
`src/core/OwnershipReconciler.ts` (drain-claim grace — the reconciler backstops a
|
|
19
|
+
dead-mid-drain owner without front-running a live one), `src/core/seamlessnessConfig.ts`
|
|
20
|
+
(SEAMLESSNESS_PROTOCOL_VERSION 1→2 per spec), `src/core/AutonomousSessions.ts`
|
|
21
|
+
(`markAutonomousInterruptedMidTask`), `src/core/types.ts`
|
|
22
|
+
(`seamlessnessFlags.ws12DrainReceive`), `src/commands/server.ts` (runner construction,
|
|
23
|
+
mesh handler, heartbeat advertisement, `_sendDrain` local/remote sender),
|
|
24
|
+
`src/server/AgentServer.ts` + `src/server/routes.ts` (ctx plumbing + the
|
|
25
|
+
`/pool/transfer` drain leg).
|
|
26
|
+
|
|
27
|
+
## Decision-point inventory
|
|
28
|
+
|
|
29
|
+
- `SessionOwnership` FSM — **modify** — adds `abort-transfer` (transferring →
|
|
30
|
+
active(self), owner-only, epoch+1). The emergency-stop escape: an aborted transfer
|
|
31
|
+
leaves NOTHING split, and the epoch bump fences a stale target claim.
|
|
32
|
+
- `MeshRpc` RBAC — **modify** — `drain` is router-only with its own refusal reason.
|
|
33
|
+
Reach ≠ authority: the receiver's CAS re-validates ownership + epoch regardless.
|
|
34
|
+
- `SessionDrainRunner` — **add** — the owner-side drain decision sequence (refuse
|
|
35
|
+
not-owner / stale-epoch / cas-lost; abort on emergency stop EVERY poll; forced
|
|
36
|
+
close only at the bound, with the honest marker + ONE notice).
|
|
37
|
+
- `/pool/transfer` drain leg — **modify** — orders the drain BEFORE the pin when the
|
|
38
|
+
owner can drain; 409 failed-needs-retry ONLY on the emergency-stop abort; every
|
|
39
|
+
other failure degrades to today's pin path (recorded in the response, never blocking).
|
|
40
|
+
- `OwnershipReconciler` — **modify** — transferring-to-me claim held back by the
|
|
41
|
+
drain grace window (45s = bound + slack) so it backstops without front-running.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Over-block
|
|
46
|
+
|
|
47
|
+
**What legitimate inputs does this reject that it shouldn't?**
|
|
48
|
+
|
|
49
|
+
- A transfer ordered while ownership raced (stale epoch) is refused by the drain and
|
|
50
|
+
DEGRADES to today's pin path — the transfer still happens, just without the drain.
|
|
51
|
+
No new rejection surface for the caller except the deliberate emergency-stop 409.
|
|
52
|
+
- The 409 abort path requires the operator's own emergency stop within the last 120s
|
|
53
|
+
— the only input it "rejects" is the move the operator just halted. A STALE stop
|
|
54
|
+
flag (>120s) never vetoes (the freshness bound exists precisely for this).
|
|
55
|
+
- The drain bound (30s default) force-closes a session mid-turn rather than blocking
|
|
56
|
+
the transfer forever — the spec's explicit choice; the honest notice + the
|
|
57
|
+
`interrupted_mid_task` marker are the compensations. No issue identified beyond
|
|
58
|
+
the spec-accepted forced close.
|
|
59
|
+
|
|
60
|
+
## 2. Under-block
|
|
61
|
+
|
|
62
|
+
**What failure modes does this still miss?**
|
|
63
|
+
|
|
64
|
+
- The spec's full Track-H release gate ("final context flush durably replicated
|
|
65
|
+
before barrier release") is implemented as drain-completion release with the
|
|
66
|
+
target's spawn-time history fetch + working-set pull carrying context — the
|
|
67
|
+
TransferOrchestrator's ledger-flush dependency does not exist yet. Honest bound,
|
|
68
|
+
named in the runner header. <!-- tracked: CMT-1416 -->
|
|
69
|
+
- `sessionQuiet` reads tmux activity (isSessionActivelyWorking); a session that
|
|
70
|
+
*looks* idle mid-thought (long tool call quiescing the pane) can be closed at a
|
|
71
|
+
non-ideal boundary. Mitigated by the resume-side history fetch; same signal the
|
|
72
|
+
reaper already trusts.
|
|
73
|
+
- A drain that lands the claim but whose pin-set then fails on the holder leaves
|
|
74
|
+
ownership@target with no pin — the WS1.3 reconciler converges it (this is the
|
|
75
|
+
exact divergence class it ships for).
|
|
76
|
+
|
|
77
|
+
## 3. Level-of-abstraction fit
|
|
78
|
+
|
|
79
|
+
The runner is a pure injected-deps sequence in core (unit-testable, no transport);
|
|
80
|
+
transport lives in MeshRpc (where every other verb lives); the sender leg lives in
|
|
81
|
+
the route that already owns transfer semantics. The drain BARRIER reuses the queue's
|
|
82
|
+
existing ownership-contention hold — no parallel queueing machinery was built (the
|
|
83
|
+
level-of-abstraction note in the runner header records why TransferOrchestrator was
|
|
84
|
+
NOT used: its ledger deps don't exist; the runner can later become its drain dep).
|
|
85
|
+
|
|
86
|
+
## 4. Signal vs authority compliance
|
|
87
|
+
|
|
88
|
+
The drain verb carries real authority (it closes a session) — so authority is
|
|
89
|
+
LAYERED, never brittle-single-check: RBAC proves the sender may ask (router-only);
|
|
90
|
+
the runner's CAS fence proves the ask still matches reality (owner + exact epoch);
|
|
91
|
+
the FSM proves the transition is legal; the emergency stop preempts at every poll;
|
|
92
|
+
and the forced path is bounded + marked + noticed. A failed/refused drain never
|
|
93
|
+
blocks the transfer — it degrades to the pre-existing path (failure of the new
|
|
94
|
+
machinery cannot deny the old capability).
|
|
95
|
+
|
|
96
|
+
## 5. Interactions
|
|
97
|
+
|
|
98
|
+
- **WS1.3 reconciler:** the transferring-to-me claim now waits out the drain grace
|
|
99
|
+
(45s) so a LIVE drain is never front-run; a dead-mid-drain owner is still
|
|
100
|
+
backstopped (grace expiry → reconciler claim → transfer completes). Tested.
|
|
101
|
+
- **P19 closeout breaker (#1092):** after a drained transfer the local session is
|
|
102
|
+
already closed — the closeout finds nothing; no double-close (terminateSession is
|
|
103
|
+
CAS'd on live status).
|
|
104
|
+
- **Durable queue:** route() queues inbound while status='transferring'
|
|
105
|
+
(ownership-contention) — the drain window IS the barrier; the claim is the release
|
|
106
|
+
point. No new queue states introduced.
|
|
107
|
+
- **Emergency stop:** the abort CAS can lose to an already-landed claim (grace
|
|
108
|
+
raced) — reported honestly as `abort-cas-lost`; the stop's own machinery still
|
|
109
|
+
halts local work.
|
|
110
|
+
- **Double-drain:** a re-delivered drain for the SAME transfer resumes the wait loop
|
|
111
|
+
idempotently (no second CAS); a drain for a DIFFERENT transfer dies at the epoch
|
|
112
|
+
fence.
|
|
113
|
+
|
|
114
|
+
## 6. External surfaces
|
|
115
|
+
|
|
116
|
+
- New mesh verb `drain` (signed, router-only). An OLD peer answers 501 no-handler →
|
|
117
|
+
the sender degrades to today's path; an old SENDER never emits the verb. The
|
|
118
|
+
capability flag (`ws12DrainReceive`, heartbeat-advertised from runner presence)
|
|
119
|
+
means a doomed order is normally never sent at all. SEAMLESSNESS_PROTOCOL_VERSION
|
|
120
|
+
bumped 1→2 per the spec's skew table.
|
|
121
|
+
- `/pool/transfer` response grows `drain` + may now 409 with `failedNeedsRetry` on
|
|
122
|
+
an emergency-stop abort; existing fields unchanged (additive).
|
|
123
|
+
- Timing: the route awaits the drain (≤ bound + slack; remote call capped at 50s) —
|
|
124
|
+
a transfer of an active conversation now takes up to ~30-50s instead of instant
|
|
125
|
+
paperwork. That is the feature: the paperwork-instant transfer was the half-move.
|
|
126
|
+
The route carries its own request-timeout budget (`POOL_TRANSFER_TIMEOUT_MS`
|
|
127
|
+
= 75s, above the 50s remote cap) so the middleware never 408s mid-drain
|
|
128
|
+
(second-pass concern #1).
|
|
129
|
+
|
|
130
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
131
|
+
|
|
132
|
+
**Replicated/coordinated by design — this IS the multi-machine path.** Ownership
|
|
133
|
+
moves through the epoch-fenced CAS registry (single-router topology); placement
|
|
134
|
+
history journals on every CAS (call-site pairing); the capability travels in the
|
|
135
|
+
authenticated heartbeat (fixed-size boolean — Phase C: no per-topic inventory, works
|
|
136
|
+
for N cloud VMs); the drain order is a signed mesh verb; takeover-without-consent
|
|
137
|
+
remains impossible (claim requires the FSM's named-target rule or the reconciler's
|
|
138
|
+
evidence gate). Notices: the forced-close notice goes to the TOPIC (the one voice
|
|
139
|
+
for it); no new URL surfaces.
|
|
140
|
+
|
|
141
|
+
## 8. Rollback cost
|
|
142
|
+
|
|
143
|
+
- The sender leg self-disables when no owner advertises the capability — reverting
|
|
144
|
+
the PR (or running mixed-version) restores today's pin path exactly.
|
|
145
|
+
- No durable format changes: the FSM action is additive; protocol version is
|
|
146
|
+
advisory metadata; the autonomous-file marker is an additive frontmatter line.
|
|
147
|
+
- No config flag was added for the drain itself BY DESIGN: it activates only along
|
|
148
|
+
capability advertisement, which exists only where this code runs; the pool's
|
|
149
|
+
master staging (`multiMachine.sessionPool.stage`) still gates the surrounding
|
|
150
|
+
machinery. Worst-case back-out = revert + release (no data migration).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Post-push CI fix (no-silent-fallbacks ratchet)
|
|
155
|
+
|
|
156
|
+
CI shard 3 failed the no-silent-fallbacks ratchet (469 > 468). Investigation:
|
|
157
|
+
every catch this PR ADDS is tagged `@silent-fallback-ok` (drain emergency-stop
|
|
158
|
+
flag read, runner-not-wired guard, `_sendDrain` RPC catch, /pool/transfer
|
|
159
|
+
drain-leg degrade catch, forced-close notice `.catch`) or is non-matching
|
|
160
|
+
(returns a populated object). The flagged-list set-diff shows NO genuine new
|
|
161
|
+
fallback — the +1 is a detection-window artifact: line shifts in the two edited
|
|
162
|
+
files reshape the heuristic's 20-line catch window so a pre-existing,
|
|
163
|
+
previously-uncounted catch now matches (the exact fragility the file's own
|
|
164
|
+
174→186 and 437→447 bump notes document). Baseline bumped 468→469 with that
|
|
165
|
+
justification (the sanctioned escape valve); the count only decreases from here.
|
|
166
|
+
|
|
167
|
+
## Second-pass review
|
|
168
|
+
|
|
169
|
+
**MANDATORY (ownership + session lifecycle) — independent reviewer, run on Opus 4.8.**
|
|
170
|
+
(The first attempt died on the Fable 5 outage — the reviewer subagent inherited
|
|
171
|
+
the escalated claude-fable-5 model, which Anthropic disabled mid-run under a
|
|
172
|
+
government directive; re-run cleanly on Opus.)
|
|
173
|
+
|
|
174
|
+
**Verdict: CONCERN RAISED (one real defect), everything else CONCURS.**
|
|
175
|
+
|
|
176
|
+
- **Concern #1 (real, was shippable-blocking) — `/pool/transfer` had no
|
|
177
|
+
request-timeout override for the synchronous drain.** The route awaits the
|
|
178
|
+
owner-side drain (up to the 30s bound + the 50s remote-call cap) but inherited
|
|
179
|
+
the 30s middleware default, so a clean drain would 408 mid-handler while the
|
|
180
|
+
handler kept running to completion (landing the claim + pin) — the exact
|
|
181
|
+
"408 while the handler keeps running" class the outbound/parity overrides
|
|
182
|
+
already exist to prevent. The integration tests passed only because they
|
|
183
|
+
inject a synchronous `sendDrain` mock that returns instantly, never exercising
|
|
184
|
+
the bound — a genuine coverage gap.
|
|
185
|
+
**FIXED:** added `POOL_TRANSFER_TIMEOUT_MS = 75_000` and wired `/pool/transfer`
|
|
186
|
+
into `buildRequestTimeoutOverrides()` (clears the 50s remote cap with margin);
|
|
187
|
+
added two assertions to `tests/unit/AgentServer-outbound-timeout.test.ts`
|
|
188
|
+
(the route resolves to the drain budget; the fast sibling `/pool/placement`
|
|
189
|
+
stays on the default). The fix mirrors the exact pattern the reviewer pointed
|
|
190
|
+
to and is verified against the production map + matcher.
|
|
191
|
+
|
|
192
|
+
- **CONCURRED, verified in source (reviewer notes 1–10):** the kill path (topic→
|
|
193
|
+
tmux→record resolution can't touch another topic's session; force-bypass only
|
|
194
|
+
at the bound; missing session = non-fatal skip); nothing-split-on-abort (owner-
|
|
195
|
+
only abort, epoch bump fences a stale target claim); no-theft (runner fences +
|
|
196
|
+
reconciler 45s drain grace + degrade-path never steals a live owner); barrier
|
|
197
|
+
correctness (the owner's target-named claim is FSM-legal via the confirmClaim
|
|
198
|
+
precedent; queue holds while transferring; claim is the release); emergency
|
|
199
|
+
stop (every-poll check, 120s flag freshness bound, 409 + no-pin only on abort);
|
|
200
|
+
fail-degrade (every non-abort failure shape → today's pin path); version skew
|
|
201
|
+
(501 degrade, verb never sent by old senders, honest capability advertisement,
|
|
202
|
+
registry stores + serves seamlessnessFlags); markAutonomousInterruptedMidTask
|
|
203
|
+
(atomic, idempotent, missing-file no-op); double-drain idempotency; RBAC
|
|
204
|
+
router-only with its own 403. 100 tests green across the 6 drain files (+2 in
|
|
205
|
+
the timeout test = 102).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Side-Effects Review — per-topic effort pin (--effort at spawn)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `topic-effort-pin`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `workflow adversarial reviewer (CONCUR — 1 MEDIUM fixed, see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds an optional `effort` field (enum `low|medium|high|xhigh|max`) to the
|
|
11
|
+
topic-profile system, threaded through validation, store, resolver, write
|
|
12
|
+
surface, change-classifier, orchestrator, conversational ingress, types, and the
|
|
13
|
+
two routes; and injects `--effort <level>` into the Claude Code launch argv
|
|
14
|
+
(interactive + headless builders in `frameworkSessionLaunch.ts`), resolved from
|
|
15
|
+
the topic profile at spawn in `SessionManager`/`server.ts`. Mirrors `thinkingMode`'s
|
|
16
|
+
plumbing. Operator request: Justin, topic 13481, "set ultracode for this topic"
|
|
17
|
+
(ultracode is not a CLI value; this pins the CLI ceiling `max`).
|
|
18
|
+
|
|
19
|
+
## Decision-point inventory
|
|
20
|
+
|
|
21
|
+
- Topic-profile resolution — **modify** — adds an `effort` resolution arm
|
|
22
|
+
(pin > config-default > unset); off-enum value → fail-open `undefined`.
|
|
23
|
+
- Launch argv builders — **modify** — push `--effort` for claude-code only when
|
|
24
|
+
set + enum-valid; non-claude untouched.
|
|
25
|
+
- `classifyProfileChange` — **modify** — adds a dedicated effort-only row
|
|
26
|
+
(kill+resume, none-loss when resume-ready) so it is not misclassified as a
|
|
27
|
+
thinking change (the second-pass MEDIUM finding, now fixed).
|
|
28
|
+
- Write surface / routes / ingress — **modify** — accept + validate `effort`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
Off-enum values are rejected at the write API (HTTP 400, profile unchanged) — that
|
|
34
|
+
is the intended block. No legitimate input is rejected; the five CLI-valid levels
|
|
35
|
+
all pass. `ultracode` is deliberately refused (not a CLI value).
|
|
36
|
+
|
|
37
|
+
## 2. Under-block
|
|
38
|
+
A value that is enum-valid but unsupported by the *running model* (e.g. `xhigh`
|
|
39
|
+
on a model that floors to `high`) is passed through — the CLI itself floors it
|
|
40
|
+
(documented behavior), so no instar-side block is needed. Headless one-shot/job
|
|
41
|
+
spawns do NOT thread effort (interactive-only); acceptable — per-topic effort is
|
|
42
|
+
a per-conversation pin, and the headless builder support is forward-looking
|
|
43
|
+
defense-in-depth (flagged by the reviewer as a deliberate choice, not a bug).
|
|
44
|
+
|
|
45
|
+
## 3. Level-of-abstraction fit
|
|
46
|
+
Right layer: mirrors `thinkingMode` exactly across the same files; the resolver
|
|
47
|
+
owns precedence, the launch builder owns argv, the classifier owns respawn
|
|
48
|
+
semantics. No parallel machinery invented.
|
|
49
|
+
|
|
50
|
+
## 4. Signal vs authority compliance
|
|
51
|
+
N/A as a gate — this is a config field, not a decision point. The one authority-
|
|
52
|
+
adjacent concern (a bad value reaching the CLI) is defended by three independent
|
|
53
|
+
fail-open/validate layers.
|
|
54
|
+
|
|
55
|
+
## 5. Interactions
|
|
56
|
+
- Interacts with the `thinkingMode`→`--effort` mapping in the interactive builder:
|
|
57
|
+
a direct effort pin WINS and suppresses the thinkingMode-derived mapping, so
|
|
58
|
+
`--effort` is emitted exactly once (unit-tested).
|
|
59
|
+
- The change-classifier effort row composes with model/tier/thinking rows: a
|
|
60
|
+
combined change is handled by those rows (their respawn carries the new flag);
|
|
61
|
+
only effort-only needed the new row.
|
|
62
|
+
|
|
63
|
+
## 6. External surfaces
|
|
64
|
+
- `GET /topic-profile/:id` gains `resolved.effort` + `sources.effort` (additive).
|
|
65
|
+
- POST profile/propose accept `effort` (additive; off-enum → 400).
|
|
66
|
+
- A spawned Claude session's argv gains `--effort <level>` when the topic pins it.
|
|
67
|
+
Old behavior (no pin) is byte-identical.
|
|
68
|
+
|
|
69
|
+
## Framework generality
|
|
70
|
+
|
|
71
|
+
The change DOES route through the framework abstraction (`frameworkSessionLaunch.ts`
|
|
72
|
+
builders), and is **deliberately Claude-optimizing, not Claude-leaking**:
|
|
73
|
+
|
|
74
|
+
- `--effort <low|medium|high|xhigh|max>` is a **Claude Code CLI flag**. Only the
|
|
75
|
+
`claudeCodeBuilder` (interactive) and `claudeCodeHeadlessBuilder` (headless) push
|
|
76
|
+
it. `InteractiveLaunchOptions.effort` / `HeadlessLaunchOptions.effort` are optional
|
|
77
|
+
inputs the non-Claude builders simply **ignore** — `codexCliBuilder`,
|
|
78
|
+
`geminiCliBuilder`, and `piCliBuilder` are untouched and emit no effort flag.
|
|
79
|
+
- This is the "Framework-Agnostic — and Framework-Optimizing" standard satisfied:
|
|
80
|
+
the abstraction stays framework-agnostic (every builder receives the same options
|
|
81
|
+
object; none is forced to honor a flag it doesn't have), while Claude gets its
|
|
82
|
+
native knob. Codex/Gemini have their own reasoning/effort controls
|
|
83
|
+
(e.g. codex `-c model_reasoning_effort`); wiring those is explicitly OUT OF SCOPE
|
|
84
|
+
here and would be a separate per-framework mapping (a future `resolveEffortForFramework`
|
|
85
|
+
analogous to `resolveModelForFramework`), not a generalization of this Claude flag.
|
|
86
|
+
- The topic-profile `effort` field itself is framework-agnostic (it's a profile
|
|
87
|
+
value); only its TRANSLATION to a launch flag is Claude-specific, which is the
|
|
88
|
+
correct layer for framework specificity (mirrors how `model` resolves per-framework
|
|
89
|
+
via `resolveModelForFramework`).
|
|
90
|
+
|
|
91
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
92
|
+
**proxied-on-read / machine-local-by-design.** The topic profile already resolves
|
|
93
|
+
per-machine at spawn; effort rides the same path. When a topic transfers machines,
|
|
94
|
+
its profile carrier (the existing topic-profile pull-at-acquire seam) carries
|
|
95
|
+
`effort` along with the other fields (it's in `PROFILE_FIELDS`), so the pin
|
|
96
|
+
follows the conversation. No new replication surface; no new URL/notice.
|
|
97
|
+
|
|
98
|
+
## 8. Rollback cost
|
|
99
|
+
Trivial: additive field, unset by default. Reverting the PR removes the field and
|
|
100
|
+
the argv injection; no durable migration (the field simply stops being read). A
|
|
101
|
+
stored `effort` on a profile file is ignored by older code (additive JSON).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Second-pass review
|
|
106
|
+
|
|
107
|
+
Workflow adversarial reviewer: **CONCUR** (tscClean, testsPass). Two findings:
|
|
108
|
+
- **MEDIUM — effort half-wired in `classifyProfileChange`** (in AXES but no
|
|
109
|
+
branch → effort-only change fell through to the thinkingMode row, wrong reason
|
|
110
|
+
+ wrong verification gate). **FIXED**: added a dedicated effort-only row
|
|
111
|
+
(kill+resume, none-loss when resume-ready; fresh recent-only otherwise) with an
|
|
112
|
+
effort-specific reason; +4 classifier unit tests asserting it resolves as
|
|
113
|
+
`resume`/none-loss, is NOT gated on the thinking flags, and the reason says
|
|
114
|
+
"effort change" not "thinking".
|
|
115
|
+
- **LOW — headless builder has no live caller** (interactive-only data flow).
|
|
116
|
+
Acknowledged as a deliberate, documented forward-looking choice (per-topic
|
|
117
|
+
effort is a per-conversation interactive pin), not a bug (§2 above).
|