instar 1.3.208 → 1.3.210
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 +48 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +13 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +8 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.js +20 -4
- package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -1
- package/dist/core/ClaudeCliIntelligenceProvider.d.ts +19 -0
- package/dist/core/ClaudeCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/ClaudeCliIntelligenceProvider.js +55 -3
- package/dist/core/ClaudeCliIntelligenceProvider.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +19 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +7 -3
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +15 -0
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/core/types.d.ts +15 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +6 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +76 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/A2ACheckInPolicy.d.ts +50 -0
- package/dist/threadline/A2ACheckInPolicy.d.ts.map +1 -0
- package/dist/threadline/A2ACheckInPolicy.js +51 -0
- package/dist/threadline/A2ACheckInPolicy.js.map +1 -0
- package/dist/threadline/A2ACheckInProxy.d.ts +56 -0
- package/dist/threadline/A2ACheckInProxy.d.ts.map +1 -0
- package/dist/threadline/A2ACheckInProxy.js +53 -0
- package/dist/threadline/A2ACheckInProxy.js.map +1 -0
- package/dist/threadline/A2ACheckInScheduler.d.ts +77 -0
- package/dist/threadline/A2ACheckInScheduler.d.ts.map +1 -0
- package/dist/threadline/A2ACheckInScheduler.js +90 -0
- package/dist/threadline/A2ACheckInScheduler.js.map +1 -0
- package/dist/threadline/A2ACheckInSummarizer.d.ts +48 -0
- package/dist/threadline/A2ACheckInSummarizer.d.ts.map +1 -0
- package/dist/threadline/A2ACheckInSummarizer.js +64 -0
- package/dist/threadline/A2ACheckInSummarizer.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +6 -0
- package/upgrades/1.3.209.md +66 -0
- package/upgrades/1.3.210.md +101 -0
- package/upgrades/side-effects/iris-audit-session-lifecycle.md +146 -0
- package/upgrades/side-effects/iris-audit-token-accounting.md +125 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Three connected session-lifecycle fixes, all surfaced by a token-efficiency audit
|
|
9
|
+
on a deployed agent:
|
|
10
|
+
|
|
11
|
+
1. **The Claude session builder now honors the configured default model.**
|
|
12
|
+
`claudeCodeBuilder` resolved `frameworkDefaultModels['claude-code']` but never
|
|
13
|
+
passed `--model` to the `claude` CLI — unlike the Codex and Gemini builders,
|
|
14
|
+
which both do. So a configured default model was silently dropped for every
|
|
15
|
+
interactive Claude session; they always ran the CLI's account default. The
|
|
16
|
+
builder now pins `--model <resolved>` when a default is set (tier aliases
|
|
17
|
+
`fast|balanced|capable` → `haiku|sonnet|opus`; raw model ids pass through), and
|
|
18
|
+
pushes nothing when unset so the account default is preserved. This matches the
|
|
19
|
+
documented contract on `InteractiveLaunchOptions.defaultModel`.
|
|
20
|
+
|
|
21
|
+
2. **`GET /sessions` now reports the real launched model for interactive sessions.**
|
|
22
|
+
The session record stores the model the session was *actually* launched with
|
|
23
|
+
(post Codex rate-limit swap), so after a config change you can confirm which
|
|
24
|
+
model a running session picked up.
|
|
25
|
+
|
|
26
|
+
3. **New `POST /sessions/restart-all` bulk-restart endpoint.** A running session
|
|
27
|
+
keeps the config it was spawned with — and Claude Code loads hooks/settings only
|
|
28
|
+
at session start, so a newly-added hook never engages on a live session. There
|
|
29
|
+
was no way to push every session onto new config short of waiting for the reaper
|
|
30
|
+
or refreshing each by hand. `POST /sessions/restart-all` refreshes every running,
|
|
31
|
+
Telegram-bound session through the existing `SessionRefresh` path (kill +
|
|
32
|
+
`claude --resume`, each conversation preserved), staggered to avoid a respawn
|
|
33
|
+
storm. Pass `{"excludeSession":"<tmux-name>"}` to keep the caller alive;
|
|
34
|
+
non-Telegram-bound sessions are skipped and reported in `skipped`.
|
|
35
|
+
|
|
36
|
+
`/metrics/features` now reports real per-feature token usage instead of always
|
|
37
|
+
`tokensIn:0 / tokensOut:0`. Root cause (item 1 of the Iris-audit spec): the LLM
|
|
38
|
+
funnel's metrics tap recorded latency/outcome/count but never had token data —
|
|
39
|
+
`ClaudeCliIntelligenceProvider` ran `claude -p --output-format text`, which discards
|
|
40
|
+
the `usage` block, and `IntelligenceProvider.evaluate()` returns only a string.
|
|
41
|
+
|
|
42
|
+
The provider now runs `--output-format json`, parses the answer from `.result`, and
|
|
43
|
+
surfaces token counts through a new additive optional `onUsage` callback on
|
|
44
|
+
`IntelligenceOptions` — `evaluate()` still returns `Promise<string>`, so every existing
|
|
45
|
+
caller is byte-identical. `CircuitBreakingIntelligenceProvider` (the universal LLM
|
|
46
|
+
chokepoint) passes an `onUsage` that forwards `tokensIn/tokensOut` into the existing
|
|
47
|
+
`FeatureMetricsLedger`, so per-feature token cost is now real. `tokensIn` sums the
|
|
48
|
+
input components actually processed (fresh + cache-creation + cache-read); `tokensOut`
|
|
49
|
+
is output tokens. Parsing is defensive — non-JSON or a missing `.result` falls back to
|
|
50
|
+
the raw text, so a malformed response degrades to "answer without token accounting,"
|
|
51
|
+
never a crash.
|
|
52
|
+
|
|
53
|
+
## What to Tell Your User
|
|
54
|
+
|
|
55
|
+
- **Your configured default model now actually applies to Claude sessions.** "If
|
|
56
|
+
you set a default model, my Claude sessions will launch with it now — previously
|
|
57
|
+
that setting was quietly ignored for Claude and only worked for Codex/Gemini."
|
|
58
|
+
- **One command to apply a config or hook change to everything running.** "After
|
|
59
|
+
you change my default model, disable a feature, or add a hook, I don't pick it up
|
|
60
|
+
on a session that's already running — Claude only loads settings when a session
|
|
61
|
+
starts. I can now restart all my live sessions in one go (each conversation is
|
|
62
|
+
preserved) so the new config takes effect, then confirm what each one is running."
|
|
63
|
+
|
|
64
|
+
- **The token cost screen works now.** "My per-feature cost view used to show 0 tokens
|
|
65
|
+
for everything even though my background checks clearly cost tokens — the number was
|
|
66
|
+
never being recorded. It now shows real token usage, so we can actually see which
|
|
67
|
+
checks are expensive and tune them."
|
|
68
|
+
|
|
69
|
+
## Summary of New Capabilities
|
|
70
|
+
|
|
71
|
+
| Capability | How to Use |
|
|
72
|
+
|-----------|-----------|
|
|
73
|
+
| Configured default model applies to Claude sessions | Automatic (set `frameworkDefaultModels['claude-code']`) |
|
|
74
|
+
| Per-session launched model in `GET /sessions` | `GET /sessions` → each session's `model` field |
|
|
75
|
+
| Bulk restart all running sessions onto new config | `POST /sessions/restart-all` (one session: `POST /sessions/refresh`) |
|
|
76
|
+
|
|
77
|
+
| Capability | How to Use |
|
|
78
|
+
|-----------|-----------|
|
|
79
|
+
| Real per-feature token usage | `GET /metrics/features` — `tokensIn` / `tokensOut` now populated |
|
|
80
|
+
|
|
81
|
+
## Evidence
|
|
82
|
+
|
|
83
|
+
- **Root cause (model):** `src/core/frameworkSessionLaunch.ts` `claudeCodeBuilder`
|
|
84
|
+
built `[binary, --dangerously-skip-permissions, (--resume id)]` with no `--model`,
|
|
85
|
+
while `codexCliBuilder`/`geminiBuilder` both push `--model`. Unit-proven in
|
|
86
|
+
`tests/unit/frameworkSessionLaunch.test.ts` (now asserts `--model` present when a
|
|
87
|
+
default is set, absent when unset, tier→model and raw-id pass-through).
|
|
88
|
+
- **`restart-all`:** `tests/unit/sessions-restart-all-route.test.ts` drives the real
|
|
89
|
+
Express route — validation, 202 ack with honest `scheduled`/`skipped`, exclusion,
|
|
90
|
+
and that each target is refreshed through `SessionRefresh` after the stagger.
|
|
91
|
+
- **Migration parity:** `tests/unit/PostUpdateMigrator-applyConfigToRunningSessions.test.ts`
|
|
92
|
+
proves existing agents get the awareness section on update (idempotent).
|
|
93
|
+
|
|
94
|
+
- **Root cause:** `ClaudeCliIntelligenceProvider` used `--output-format text` and the
|
|
95
|
+
`CircuitBreakingIntelligenceProvider` tap (`recordMetric`) passed no tokens, so
|
|
96
|
+
`FeatureMetricsLedger`'s token columns were always null → summed to 0.
|
|
97
|
+
- **Live CLI shape verified:** `claude -p --model haiku --output-format json` returns
|
|
98
|
+
`{ result:"ok", usage:{ input_tokens:10, output_tokens:41, cache_*:… } }`.
|
|
99
|
+
- **Tests:** `tests/unit/ClaudeCliIntelligenceProvider-jsonParse.test.ts` (parser
|
|
100
|
+
contract + defensive fallbacks) and `tests/unit/CircuitBreaking-feature-metrics-tap.
|
|
101
|
+
test.ts` (funnel forwards tokens; sums into the real ledger rollup, no longer 0).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Side-Effects Review — Iris-audit session lifecycle (PR A: items 2/3/4)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `iris-audit-session-lifecycle`
|
|
4
|
+
**Date:** `2026-06-02`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `3 independent spec reviewers (convergence round) — concur`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
PR A of the Iris-audit spec (`docs/specs/iris-audit-session-observability.md`),
|
|
11
|
+
covering items 2/3/4. Three changes: (a) `claudeCodeBuilder` now pushes `--model
|
|
12
|
+
<resolved>` when `frameworkDefaultModels['claude-code']` is set (it was silently
|
|
13
|
+
dropped before — only Codex/Gemini builders passed it), and the interactive `Session`
|
|
14
|
+
record stores the model actually launched with; (b) a new `POST /sessions/restart-all`
|
|
15
|
+
route that bulk-refreshes every running Telegram-bound session through the existing
|
|
16
|
+
`SessionRefresh` path (staggered, conversation-preserving); (c) a CLAUDE.md template
|
|
17
|
+
section + idempotent `migrateClaudeMd` backfill teaching that hooks/config load at
|
|
18
|
+
session start so applying a change requires a restart. Files: `src/core/
|
|
19
|
+
frameworkSessionLaunch.ts`, `src/core/SessionManager.ts`, `src/server/routes.ts`,
|
|
20
|
+
`src/scaffold/templates.ts`, `src/core/PostUpdateMigrator.ts`, plus tests and an
|
|
21
|
+
upgrade fragment. Decision points touched: the interactive launch-arg builder
|
|
22
|
+
(launch-shape, no block surface), the new restart-all action endpoint (real blast
|
|
23
|
+
radius — reuses the existing rate-guarded `SessionRefresh` authority), and an
|
|
24
|
+
additive idempotent doc migration.
|
|
25
|
+
|
|
26
|
+
## Decision-point inventory
|
|
27
|
+
|
|
28
|
+
- `claudeCodeBuilder` argv (frameworkSessionLaunch.ts) — **modify** — adds `--model`
|
|
29
|
+
when a default is set; pass-through otherwise. No block/allow surface.
|
|
30
|
+
- `POST /sessions/restart-all` (routes.ts) — **add** — new action endpoint; delegates
|
|
31
|
+
to the existing `SessionRefresh` (does not add a new blocking authority).
|
|
32
|
+
- interactive `Session.model` recording (SessionManager.ts) — **modify** — stores the
|
|
33
|
+
effective launched model; observational, no decision logic.
|
|
34
|
+
- `migrateClaudeMd` awareness section (PostUpdateMigrator.ts) — **add** — additive,
|
|
35
|
+
idempotent, content-sniffed text backfill; no decision logic.
|
|
36
|
+
|
|
37
|
+
## 1. Over-block
|
|
38
|
+
|
|
39
|
+
No block/allow surface — over-block not applicable. The only gating is the existing
|
|
40
|
+
`SessionRefresh` rate-guard (5/10min per session), reused unchanged; restart-all does
|
|
41
|
+
not add or alter any allow/deny logic. The Telegram-bound filter is an inclusion
|
|
42
|
+
filter (which sessions are eligible), not a rejection of legitimate input.
|
|
43
|
+
|
|
44
|
+
## 2. Under-block
|
|
45
|
+
|
|
46
|
+
No block/allow surface — under-block not applicable. restart-all intentionally skips
|
|
47
|
+
non-Telegram-bound sessions (Slack/iMessage/headless) — reported transparently in the
|
|
48
|
+
`skipped` count, not silently dropped.
|
|
49
|
+
|
|
50
|
+
## 3. Level-of-abstraction fit
|
|
51
|
+
|
|
52
|
+
restart-all sits at the right layer: it is a thin HTTP route that orchestrates the
|
|
53
|
+
existing, fully-tested `SessionRefresh` orchestrator (which owns kill+respawn,
|
|
54
|
+
rate-guarding, in-flight protection, and topic resolution). It does NOT re-implement
|
|
55
|
+
any of that — it loops the existing primitive with a stagger. The model-launch change
|
|
56
|
+
is at the builder layer where every other framework already applies `--model`. The
|
|
57
|
+
awareness change is documentation, delivered via the established `migrateClaudeMd`
|
|
58
|
+
mechanism. Nothing is at the wrong layer; no parallel re-implementation of an existing
|
|
59
|
+
primitive.
|
|
60
|
+
|
|
61
|
+
## 4. Signal vs authority compliance
|
|
62
|
+
|
|
63
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
64
|
+
|
|
65
|
+
- [x] No — this change has no block/allow surface (restart-all reuses the existing
|
|
66
|
+
`SessionRefresh` authority; the model record and the awareness section are signals/
|
|
67
|
+
documentation).
|
|
68
|
+
|
|
69
|
+
The model field on `GET /sessions` is a pure signal (observability). restart-all
|
|
70
|
+
carries the authority to kill+respawn, but that authority already exists in
|
|
71
|
+
`SessionRefresh` (used by `/sessions/refresh`); this change does not create a new
|
|
72
|
+
brittle authority — it reuses the smart, rate-guarded one. No brittle detector owns
|
|
73
|
+
blocking authority here.
|
|
74
|
+
|
|
75
|
+
## 5. Interactions
|
|
76
|
+
|
|
77
|
+
- **Shadowing:** restart-all runs N independent single-refreshes; it neither shadows
|
|
78
|
+
nor is shadowed by other `/sessions/*` routes (it only reads the session list and
|
|
79
|
+
calls `SessionRefresh`).
|
|
80
|
+
- **Double-fire:** the in-flight guard inside `SessionRefresh` prevents a second
|
|
81
|
+
refresh of the same session firing before the first completes, so restart-all can't
|
|
82
|
+
double-restart a session even if called twice.
|
|
83
|
+
- **Races:** the target set is snapshot-time; a session spawned after the snapshot is
|
|
84
|
+
untouched (documented). The reaper interaction is NOT new — restart-all reuses the
|
|
85
|
+
exact shipped `/sessions/refresh` path; a freshly respawned session has current
|
|
86
|
+
activity (not idle-reapable) and the old session is marked `killed`.
|
|
87
|
+
- **Feedback loops:** none. restart-all is a one-shot operation; it does not feed a
|
|
88
|
+
system that re-triggers it.
|
|
89
|
+
|
|
90
|
+
## 6. External surfaces
|
|
91
|
+
|
|
92
|
+
- **Other agents on the machine:** none — restart-all only acts on THIS agent's own
|
|
93
|
+
sessions (its own `state`/`SessionRefresh`).
|
|
94
|
+
- **Install base:** items 2 and 4 reach the fleet via update. Item 2 makes a
|
|
95
|
+
previously-inert config (`frameworkDefaultModels['claude-code']`) active — effect
|
|
96
|
+
appears only on a fresh session, only when the field is set, and is auditable via
|
|
97
|
+
`GET /sessions` (documented "activation safety"). Item 4 appends an idempotent
|
|
98
|
+
CLAUDE.md section.
|
|
99
|
+
- **External systems:** Telegram only insofar as restarted sessions resume their
|
|
100
|
+
Telegram-bound conversations via `claude --resume` — same as the existing single
|
|
101
|
+
refresh.
|
|
102
|
+
- **Persistent state:** the `Session` record now carries `model` for interactive
|
|
103
|
+
sessions (additive field); no schema migration needed (optional field already on the
|
|
104
|
+
`Session` type). The CLAUDE.md migration appends text idempotently.
|
|
105
|
+
- **Timing:** the restart-all stagger (500 + i·750 ms) spreads respawns; no fleet-wide
|
|
106
|
+
concurrency ceiling in v1 (per-session rate-guard is the aggregate backstop; named
|
|
107
|
+
as a known bound in the spec).
|
|
108
|
+
|
|
109
|
+
## 7. Rollback cost
|
|
110
|
+
|
|
111
|
+
- **Hot-fix release:** pure code revert shipped as the next patch.
|
|
112
|
+
- **Data migration:** none — `Session.model` is an optional field already in the type;
|
|
113
|
+
reverting just stops populating it for interactive sessions.
|
|
114
|
+
- **Agent state repair:** none. The CLAUDE.md migration is idempotent and additive;
|
|
115
|
+
reverting the migrator leaves already-patched CLAUDE.md files harmlessly carrying the
|
|
116
|
+
section (no removal needed).
|
|
117
|
+
- **User visibility:** no user-visible regression during the rollback window. Item 2's
|
|
118
|
+
effect only manifests on a fresh session; reverting simply returns Claude sessions to
|
|
119
|
+
the CLI account default.
|
|
120
|
+
|
|
121
|
+
## Conclusion
|
|
122
|
+
|
|
123
|
+
The review produced no design changes to PR A's code — the changes are additive and
|
|
124
|
+
reuse existing authorities. The convergence round refined the SPEC's documentation
|
|
125
|
+
(snapshot semantics, fleet bound, reaper-reuse, activation safety, idempotency
|
|
126
|
+
pattern). Clear to ship. Item 1 (token accounting) ships as the sibling PR B against
|
|
127
|
+
the same spec.
|
|
128
|
+
|
|
129
|
+
## Second-pass review (if required)
|
|
130
|
+
|
|
131
|
+
**Reviewer:** 3 independent spec reviewers (design-correctness, safety/blast-radius,
|
|
132
|
+
completeness/standards-fit)
|
|
133
|
+
**Independent read of the artifact: concur**
|
|
134
|
+
|
|
135
|
+
All three reviewers' material findings were resolved in-spec; the two "item 1 not
|
|
136
|
+
implemented" criticals were a misunderstanding of the intended PR A / PR B phasing
|
|
137
|
+
(item 1 is PR B, tracked via `deferrals-tracked`). No structural redesign. See
|
|
138
|
+
`docs/specs/reports/iris-audit-session-observability-convergence.md`.
|
|
139
|
+
|
|
140
|
+
## Evidence pointers
|
|
141
|
+
|
|
142
|
+
- `tests/unit/frameworkSessionLaunch.test.ts` — `--model` set/unset/tier/raw/with-resume.
|
|
143
|
+
- `tests/unit/sessions-restart-all-route.test.ts` — real Express route behavior.
|
|
144
|
+
- `tests/unit/PostUpdateMigrator-applyConfigToRunningSessions.test.ts` — migration idempotency.
|
|
145
|
+
- `tests/unit/feature-delivery-completeness.test.ts` — template↔migrator parity (green).
|
|
146
|
+
- `tsc --noEmit` clean; `pnpm build` clean.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Side-Effects Review — Iris-audit token usage accounting (PR B: item 1)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `iris-audit-token-accounting`
|
|
4
|
+
**Date:** `2026-06-02`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `convergence reviewers (shared spec) — concur`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
PR B of the Iris-audit spec (`docs/specs/iris-audit-session-observability.md`), item
|
|
11
|
+
1: make `/metrics/features` report real per-feature token usage instead of always 0.
|
|
12
|
+
`ClaudeCliIntelligenceProvider` switches `claude -p` from `--output-format text` to
|
|
13
|
+
`json` and parses the result object for the answer (`.result`) and `usage`; a new
|
|
14
|
+
additive optional `onUsage` callback on `IntelligenceOptions` surfaces the token
|
|
15
|
+
counts. `CircuitBreakingIntelligenceProvider` (the universal LLM funnel) passes an
|
|
16
|
+
`onUsage` that captures usage and forwards `tokensIn/tokensOut` into the existing
|
|
17
|
+
`FeatureMetricsLedger` tap. Files: `src/core/types.ts`, `src/core/
|
|
18
|
+
ClaudeCliIntelligenceProvider.ts`, `src/core/CircuitBreakingIntelligenceProvider.ts`,
|
|
19
|
+
plus tests. The decision points touched are observability-only — no gate changes.
|
|
20
|
+
|
|
21
|
+
## Decision-point inventory
|
|
22
|
+
|
|
23
|
+
- `IntelligenceOptions.onUsage` (types.ts) — **add** — optional callback; no decision
|
|
24
|
+
logic.
|
|
25
|
+
- `ClaudeCliIntelligenceProvider.evaluate` output parsing (ClaudeCliIntelligence
|
|
26
|
+
Provider.ts) — **modify** — text→json + `parseJsonResult`; affects the return-value
|
|
27
|
+
extraction for EVERY Claude-backed LLM call, but the returned text is unchanged.
|
|
28
|
+
- `CircuitBreakingIntelligenceProvider` metrics tap (CircuitBreakingIntelligence
|
|
29
|
+
Provider.ts) — **modify** — now forwards token counts; observability only.
|
|
30
|
+
|
|
31
|
+
## 1. Over-block
|
|
32
|
+
|
|
33
|
+
No block/allow surface — over-block not applicable. This is pure observability
|
|
34
|
+
plumbing; nothing is rejected.
|
|
35
|
+
|
|
36
|
+
## 2. Under-block
|
|
37
|
+
|
|
38
|
+
No block/allow surface — under-block not applicable. (If usage is ever absent or
|
|
39
|
+
unparseable, the only effect is that a call's token cost is omitted — recorded as the
|
|
40
|
+
pre-existing 0/null — never a wrong block.)
|
|
41
|
+
|
|
42
|
+
## 3. Level-of-abstraction fit
|
|
43
|
+
|
|
44
|
+
Right layer. Token usage originates in the provider that runs the CLI
|
|
45
|
+
(`ClaudeCliIntelligenceProvider`) — the only place it exists — and is surfaced via the
|
|
46
|
+
same `IntelligenceOptions` contract every provider already takes. The funnel
|
|
47
|
+
(`CircuitBreakingIntelligenceProvider`) is the established single chokepoint that
|
|
48
|
+
already records the per-feature metric; threading tokens through it instruments ALL
|
|
49
|
+
LLM features at once (Structure > Willpower) rather than each call site. The ledger is
|
|
50
|
+
reused unchanged (it already had token columns waiting for data).
|
|
51
|
+
|
|
52
|
+
## 4. Signal vs authority compliance
|
|
53
|
+
|
|
54
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
55
|
+
|
|
56
|
+
- [x] No — this change has no block/allow surface (it is observability: a signal
|
|
57
|
+
producer feeding the metrics ledger).
|
|
58
|
+
|
|
59
|
+
The metrics ledger is a read-only signal surface (`/metrics/features`); it never gates.
|
|
60
|
+
Token accounting strictly adds data; it holds no authority.
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- **Shadowing:** none — parsing happens inside `evaluate`'s existing resolve path; no
|
|
65
|
+
new check before/after another.
|
|
66
|
+
- **Double-fire:** `onUsage` fires at most once per successful call (only when usage
|
|
67
|
+
is present with non-zero tokens); the funnel composes with (does not clobber) any
|
|
68
|
+
caller-supplied `onUsage`.
|
|
69
|
+
- **Races:** none — synchronous parse within the call's own callback.
|
|
70
|
+
- **Feedback loops:** none — recorded metrics are read-only; nothing consumes them to
|
|
71
|
+
re-drive an LLM call.
|
|
72
|
+
|
|
73
|
+
## 6. External surfaces
|
|
74
|
+
|
|
75
|
+
- **The claude CLI invocation changes** from `--output-format text` to `json`. This is
|
|
76
|
+
the load-bearing risk: the return value is now extracted from `.result`. Mitigation:
|
|
77
|
+
`parseJsonResult` is defensive — non-JSON or missing `.result` falls back to the raw
|
|
78
|
+
trimmed stdout (the prior behavior), so a malformed/partial response degrades to
|
|
79
|
+
"answer without token accounting," never a crash or a JSON blob leaking to callers.
|
|
80
|
+
The empirical CLI shape was verified live (`{ result, usage:{ input_tokens,
|
|
81
|
+
cache_creation_input_tokens, cache_read_input_tokens, output_tokens } }`).
|
|
82
|
+
- **Rate-limit detection unaffected:** the circuit breaker's classifier reads
|
|
83
|
+
`stderr`, not stdout — the format switch does not touch it (verified in the provider;
|
|
84
|
+
the error path is unchanged).
|
|
85
|
+
- **Other agents / install base:** ships fleet-wide via update; every LLM-backed
|
|
86
|
+
feature's calls now carry token counts into `/metrics/features`. No behavior change
|
|
87
|
+
to what those features decide — only the cost telemetry becomes real.
|
|
88
|
+
- **Persistent state:** writes to the existing `FeatureMetricsLedger` SQLite token
|
|
89
|
+
columns (previously always null); no schema change (columns already existed).
|
|
90
|
+
|
|
91
|
+
## 7. Rollback cost
|
|
92
|
+
|
|
93
|
+
- **Hot-fix release:** pure code revert shipped as the next patch (revert the three
|
|
94
|
+
core files). No persistent-state migration — the ledger columns simply return to
|
|
95
|
+
null. No agent-state repair, no user-visible regression.
|
|
96
|
+
- The only externally-observable change is `claude -p --output-format json`; reverting
|
|
97
|
+
restores `text`. Because `parseJsonResult` already tolerates non-JSON, even a partial
|
|
98
|
+
rollout (some agents on json, some on text) is safe.
|
|
99
|
+
|
|
100
|
+
## Conclusion
|
|
101
|
+
|
|
102
|
+
The review produced no design changes — the change is additive, observability-only, and
|
|
103
|
+
defensively parsed. The single real risk (the CLI output-format switch) is contained by
|
|
104
|
+
the raw-stdout fallback and verified against the live CLI shape. Clear to ship. This is
|
|
105
|
+
the sibling of PR A under the same converged + approved spec.
|
|
106
|
+
|
|
107
|
+
## Second-pass review (if required)
|
|
108
|
+
|
|
109
|
+
**Reviewer:** the 3 spec convergence reviewers (shared spec) — concur on item 1's
|
|
110
|
+
design (additive onUsage side-channel, no evaluate() return-type change). The one
|
|
111
|
+
open question they raised — does onUsage fire on cache-only reads — is resolved here:
|
|
112
|
+
`parseJsonResult` sums cache_read/cache_creation INTO tokensIn (they are real input
|
|
113
|
+
cost) and fires whenever any count is non-zero, so cache-dominated calls are counted,
|
|
114
|
+
not dropped.
|
|
115
|
+
|
|
116
|
+
## Evidence pointers
|
|
117
|
+
|
|
118
|
+
- `tests/unit/ClaudeCliIntelligenceProvider-jsonParse.test.ts` — parser contract
|
|
119
|
+
(extract .result, sum input components, defensive fallbacks, onUsage firing rules).
|
|
120
|
+
- `tests/unit/CircuitBreaking-feature-metrics-tap.test.ts` — funnel forwards tokens to
|
|
121
|
+
the recorder, composes with caller onUsage, omits when absent, and sums into the real
|
|
122
|
+
ledger rollup (tokensIn/tokensOut no longer 0).
|
|
123
|
+
- Live CLI shape verified: `claude -p --model haiku --output-format json` →
|
|
124
|
+
`{ result:"ok", usage:{ input_tokens:10, output_tokens:41, ... } }`.
|
|
125
|
+
- `tsc --noEmit` clean.
|