instar 1.3.121 → 1.3.123
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/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +16 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +34 -2
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/TopicResumeMap.d.ts.map +1 -1
- package/dist/core/TopicResumeMap.js +18 -2
- package/dist/core/TopicResumeMap.js.map +1 -1
- package/dist/messaging/relayContentDedup.d.ts +51 -0
- package/dist/messaging/relayContentDedup.d.ts.map +1 -0
- package/dist/messaging/relayContentDedup.js +0 -0
- package/dist/messaging/relayContentDedup.js.map +1 -0
- package/dist/monitoring/frameworkActivitySignals.d.ts.map +1 -1
- package/dist/monitoring/frameworkActivitySignals.js +12 -1
- package/dist/monitoring/frameworkActivitySignals.js.map +1 -1
- package/dist/providers/adapters/openai-codex/observability/codexModelSwapPolicy.d.ts +83 -0
- package/dist/providers/adapters/openai-codex/observability/codexModelSwapPolicy.d.ts.map +1 -0
- package/dist/providers/adapters/openai-codex/observability/codexModelSwapPolicy.js +93 -0
- package/dist/providers/adapters/openai-codex/observability/codexModelSwapPolicy.js.map +1 -0
- package/dist/providers/adapters/openai-codex/observability/codexRateLimitReader.d.ts +95 -0
- package/dist/providers/adapters/openai-codex/observability/codexRateLimitReader.d.ts.map +1 -0
- package/dist/providers/adapters/openai-codex/observability/codexRateLimitReader.js +170 -0
- package/dist/providers/adapters/openai-codex/observability/codexRateLimitReader.js.map +1 -0
- package/dist/providers/adapters/openai-codex/observability/sessionPaths.d.ts +10 -0
- package/dist/providers/adapters/openai-codex/observability/sessionPaths.d.ts.map +1 -1
- package/dist/providers/adapters/openai-codex/observability/sessionPaths.js +44 -1
- package/dist/providers/adapters/openai-codex/observability/sessionPaths.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +11 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +58 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ThreadResumeMap.d.ts.map +1 -1
- package/dist/threadline/ThreadResumeMap.js +19 -7
- package/dist/threadline/ThreadResumeMap.js.map +1 -1
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +10 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.122.md +86 -0
- package/upgrades/1.3.123.md +105 -0
- package/upgrades/side-effects/codex-exec-json-silence-signal.md +64 -0
- package/upgrades/side-effects/codex-model-swap.md +134 -0
- package/upgrades/side-effects/codex-resume-jsonl-exists.md +68 -0
- package/upgrades/side-effects/codex-usage-visibility.md +107 -0
- package/upgrades/side-effects/ship-gate-sha-mismatch-error.md +54 -0
- package/upgrades/side-effects/threadline-relay-content-dedup.md +93 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Instar Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
**Codex usage is now readable over HTTP — the codex `/status` rate-limit
|
|
8
|
+
windows, without the interactive TUI.** Codex has no public usage API, but the
|
|
9
|
+
codex CLI persists the authoritative account rate-limit windows it gets back
|
|
10
|
+
from OpenAI — primary (5h rolling) and secondary (weekly) — into each session
|
|
11
|
+
rollout's `token_count` events. A new reader
|
|
12
|
+
(`readLatestCodexUsage`) finds the newest rollout, reads only its tail (the
|
|
13
|
+
windows are appended per-turn, so the freshest is near the end), and returns a
|
|
14
|
+
structured snapshot. A new read-only route, `GET /codex/usage`, surfaces it:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
{ "available": true,
|
|
18
|
+
"usage": {
|
|
19
|
+
"primary": { "usedPercent": 13, "remainingPercent": 87, "windowMinutes": 300, "resetsAtIso": "…", "resetsInSeconds": … },
|
|
20
|
+
"secondary": { "usedPercent": 93, "remainingPercent": 7, "windowMinutes": 10080, "resetsAtIso": "…", "resetsInSeconds": … },
|
|
21
|
+
"model": "gpt-5.5", "planType": "plus", "rateLimitReachedType": null } }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`available:false` (still HTTP 200, never 503 — it is a disk reader) means there
|
|
25
|
+
is simply no codex session data on disk yet (e.g. a pure-Claude agent). This is
|
|
26
|
+
the data an agent needs to answer "how much codex usage is left?" and to drive a
|
|
27
|
+
model-swap when a window is exhausted (`rateLimitReachedType` non-null, or
|
|
28
|
+
`secondary.remainingPercent` low).
|
|
29
|
+
|
|
30
|
+
The route is classified in the capability index (discoverable via
|
|
31
|
+
`GET /capabilities`), surfaced in the CLAUDE.md template for new agents, and
|
|
32
|
+
back-filled into existing agents' CLAUDE.md by the migrator (so deployed agents
|
|
33
|
+
learn it on update). Also restores the feature-delivery-completeness test to
|
|
34
|
+
green by tracking two prior dark/operational migrator sections
|
|
35
|
+
(`Autonomous-fix loop`, `Multi-Machine Session Pool`) that had not been
|
|
36
|
+
registered.
|
|
37
|
+
|
|
38
|
+
**Codex agents can auto-swap to a fallback model when their weekly window is
|
|
39
|
+
exhausted (ships DARK).** Building on the usage reader above: at codex session
|
|
40
|
+
launch, if the main model's weekly window is spent (or codex flags a limit hit),
|
|
41
|
+
the next session launches on a configured fallback model that draws on a separate
|
|
42
|
+
quota bucket — instead of stalling. The decision is a pure policy
|
|
43
|
+
(`resolveCodexLaunchModel`) wired into both codex launch paths in
|
|
44
|
+
`SessionManager` (headless + interactive). Gated behind
|
|
45
|
+
`codex.rateLimitModelSwap.{enabled,fallbackModel,weeklyRemainingThreshold}`;
|
|
46
|
+
off by default with zero spawn-path overhead. The fallback model id is operator
|
|
47
|
+
config — never hardcoded — because the exact id and its subscription
|
|
48
|
+
availability are the account owner's to confirm.
|
|
49
|
+
|
|
50
|
+
## Summary of New Capabilities
|
|
51
|
+
|
|
52
|
+
- `GET /codex/usage` — the freshest codex account rate-limit snapshot (primary
|
|
53
|
+
5h + secondary weekly windows, with used/remaining percent, window length,
|
|
54
|
+
absolute + relative reset, plan type, and which window — if any — is
|
|
55
|
+
exhausted). Read-only; never mutates session state.
|
|
56
|
+
- An optional `?codexHome=` query parameter targets a specific `$CODEX_HOME`
|
|
57
|
+
(defaults to `~/.codex`).
|
|
58
|
+
- Agent-awareness: the capability appears in `GET /capabilities`, the CLAUDE.md
|
|
59
|
+
template, and is migrated into existing agents.
|
|
60
|
+
- Codex rate-limit model-swap (dark): `codex.rateLimitModelSwap` —
|
|
61
|
+
auto-launch the next codex session on `fallbackModel` when the weekly window's
|
|
62
|
+
remaining percent is at or below `weeklyRemainingThreshold` (default 10) or
|
|
63
|
+
codex reports a limit hit. Off by default; best-effort and fail-safe (a usage
|
|
64
|
+
read never blocks a launch).
|
|
65
|
+
|
|
66
|
+
## What to Tell Your User
|
|
67
|
+
|
|
68
|
+
You can now ask me how much codex usage is left and I can answer directly,
|
|
69
|
+
without opening the codex status screen myself — I read the same five-hour and
|
|
70
|
+
weekly limit windows the codex tool reports. I can tell you the percent used and
|
|
71
|
+
remaining on each window, when each one resets, which model is in effect, and
|
|
72
|
+
whether either window is currently maxed out. This also gives me the signal I
|
|
73
|
+
need to switch models before the weekly limit runs out. Nothing changes for you
|
|
74
|
+
on update; the capability is simply there when you need it.
|
|
75
|
+
|
|
76
|
+
And if you want it, I can now do that switch automatically: when a codex agent's
|
|
77
|
+
weekly model quota is nearly spent, the next session it starts can launch on a
|
|
78
|
+
backup model that has its own separate budget, so it keeps working instead of
|
|
79
|
+
stalling. This is off until you turn it on and tell me which backup model to use
|
|
80
|
+
— the exact name and whether it works on your subscription is something only you
|
|
81
|
+
can confirm — so nothing changes by default.
|
|
82
|
+
|
|
83
|
+
## Evidence
|
|
84
|
+
|
|
85
|
+
**Source of truth.** Inspected a live codex rollout
|
|
86
|
+
(`~/.codex/sessions/2026/05/30/rollout-…jsonl`) and confirmed each
|
|
87
|
+
`token_count` event carries `payload.rate_limits` with `primary`
|
|
88
|
+
(window_minutes 300) and `secondary` (window_minutes 10080) sub-objects, each
|
|
89
|
+
with `used_percent` and `resets_at`, plus `plan_type` and
|
|
90
|
+
`rate_limit_reached_type`. The observed `secondary.used_percent` of 93 (i.e. 7%
|
|
91
|
+
weekly remaining) matched the codex `/status` screen at the same moment —
|
|
92
|
+
verifying the on-disk windows equal the TUI's.
|
|
93
|
+
|
|
94
|
+
**Before:** there was no way for an agent to read codex usage programmatically —
|
|
95
|
+
the only surface was the interactive TUI (`UsageMeterProvider.isAuthoritative()`
|
|
96
|
+
returns false and only does local token accounting), so an agent could not
|
|
97
|
+
answer "where does codex usage sit?" or react to an exhausted window.
|
|
98
|
+
|
|
99
|
+
**After:** `GET /codex/usage` returns the structured snapshot above. Verified by
|
|
100
|
+
a 3-tier test suite (16 tests, all green): unit tests parse the latest
|
|
101
|
+
`token_count` (newest event wins; malformed lines skipped; missing-window
|
|
102
|
+
tolerated; reached-type surfaced), an integration test drives the route over
|
|
103
|
+
HTTP against a rollout fixture (data → `available:true`; none → `available:false`
|
|
104
|
+
+ 200), and a Tier-3 lifecycle test boots the real AgentServer and confirms the
|
|
105
|
+
route is alive (200, Bearer-gated, read-only POST→404).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Side-Effects Review — silence sentinel recognizes codex exec --json sessions
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `codex-exec-json-silence-signal`
|
|
4
|
+
**Date:** `2026-05-30`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
**Second-pass reviewer:** `instar-echo second-pass checklist`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The ActiveWorkSilenceSentinel only watches sessions that `looksActivelyWorking`;
|
|
11
|
+
an idle frame is marked `paused` and skipped. The codex activity signatures in
|
|
12
|
+
`frameworkActivitySignals.ts` matched only the interactive TUI. Codex job and
|
|
13
|
+
autonomous-spawn sessions run `codex exec --json`, which emits a JSON event
|
|
14
|
+
stream (`{"type":"thread.started"}`, `{"type":"turn.started"}`,
|
|
15
|
+
`{"type":"item.completed"}`, ...) that matched nothing — so a working exec-json
|
|
16
|
+
session read as not-active, was marked paused, and the silence watchdog never
|
|
17
|
+
considered it. A wedged exec-json job (frozen mid-turn ~8.5h, 2026-05-30) was
|
|
18
|
+
invisible.
|
|
19
|
+
|
|
20
|
+
The change adds the event-stream namespaces to
|
|
21
|
+
`CODEX_CLI_SIGNAL.toolCallOrSpinner` (`"type":"thread."`/`"turn."`/`"item."`),
|
|
22
|
+
keeping every existing TUI pattern. A working exec-json session is now active and
|
|
23
|
+
silence-eligible; when it freezes the sentinel detects the output gap.
|
|
24
|
+
|
|
25
|
+
## Decision-point inventory
|
|
26
|
+
|
|
27
|
+
- `CODEX_CLI_SIGNAL.toolCallOrSpinner` — modify — extends the "is codex actively
|
|
28
|
+
working" signature to cover the exec-json event stream, not just the TUI.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
"Over-block" here would mean flagging a session as frozen when it is fine. The
|
|
35
|
+
change cannot cause that: it only makes a session ELIGIBLE for silence checking
|
|
36
|
+
(by counting it as active). The actual freeze decision is still the
|
|
37
|
+
output-silence threshold plus the tracker's observed-change requirement — both
|
|
38
|
+
unchanged. A live, still-streaming exec-json session keeps advancing
|
|
39
|
+
`lastChangeAt` and is never flagged.
|
|
40
|
+
|
|
41
|
+
## 2. Under-block
|
|
42
|
+
|
|
43
|
+
A truly-idle exec-json pane that happens to contain a stale event marker could
|
|
44
|
+
read as active. This does not cause a false alarm (the tracker requires an
|
|
45
|
+
observed active→silent transition; a pane frozen before first sighting stays
|
|
46
|
+
`lastOutputAt: 0` and is skipped). The critical 2026-05-23 guard — never match
|
|
47
|
+
the idle model-name status line — is preserved and explicitly tested.
|
|
48
|
+
|
|
49
|
+
## 3. Level-of-abstraction fit
|
|
50
|
+
|
|
51
|
+
The codex event-stream signature belongs in `frameworkActivitySignals.ts`, the
|
|
52
|
+
single module that owns per-framework activity patterns. The framework-agnostic
|
|
53
|
+
detection in `sentinelWiring`/`ActiveWorkSilenceSentinel` is untouched — only the
|
|
54
|
+
codex signature it consumes is extended, so every framework's detection path
|
|
55
|
+
stays uniform.
|
|
56
|
+
|
|
57
|
+
## 4. Signal vs authority compliance
|
|
58
|
+
|
|
59
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
60
|
+
|
|
61
|
+
- [x] This is a detector signature, not authority. It feeds the existing silence
|
|
62
|
+
detector; it issues no kill and no block. Its only effect is to let the
|
|
63
|
+
detector SEE codex exec-json sessions it was previously blind to. The
|
|
64
|
+
downstream nudge/escalation policy is unchanged and already tone-gated.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Side-Effects Review — Codex rate-limit model-swap (directive #4b)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `codex-model-swap`
|
|
4
|
+
**Date:** 2026-05-30
|
|
5
|
+
**Author:** Echo (instar-dev agent)
|
|
6
|
+
**Second-pass reviewer:** REQUIRED (touches session spawn / lifecycle) — see verdict at end
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
At codex session launch, if the agent's main model has exhausted its weekly
|
|
11
|
+
window, launch the next session on a configured fallback model (separate quota
|
|
12
|
+
bucket) instead of stalling. New pure policy
|
|
13
|
+
(`src/providers/adapters/openai-codex/observability/codexModelSwapPolicy.ts`) +
|
|
14
|
+
a private `SessionManager.resolveCodexLaunchModel` helper wired into BOTH codex
|
|
15
|
+
launch paths (`spawnSession`→`buildHeadlessLaunch`,
|
|
16
|
+
`spawnInteractiveSession`→`buildInteractiveLaunch`). Consumes the on-disk
|
|
17
|
+
`readLatestCodexUsage` reader (shipped in CODEX-USAGE-VISIBILITY). Ships DARK
|
|
18
|
+
behind `config.codex.rateLimitModelSwap.{enabled,fallbackModel,weeklyRemainingThreshold}`.
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
- `SessionManager.resolveCodexLaunchModel` (NEW decision) — picks the launch
|
|
23
|
+
model for a codex spawn. Add. It is a model SELECTION, not a block/allow gate:
|
|
24
|
+
the only outcomes are "requested model" or "configured fallback model". Both
|
|
25
|
+
spawn paths consume it (pass-through for non-codex / disabled).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1. Over-block
|
|
30
|
+
|
|
31
|
+
**No block/allow surface — over-block not applicable.** The policy never rejects
|
|
32
|
+
or cancels a launch. Its worst "wrong" outcome is launching on the fallback
|
|
33
|
+
model when it didn't strictly need to (e.g. weekly at exactly the threshold) —
|
|
34
|
+
which still produces a working session, just on the other quota bucket.
|
|
35
|
+
|
|
36
|
+
## 2. Under-block
|
|
37
|
+
|
|
38
|
+
**Not a blocker.** The closest "miss": the swap only takes effect at the NEXT
|
|
39
|
+
launch — a session already mid-turn on an exhausted model still fails that turn
|
|
40
|
+
(it can't change model mid-flight). That is inherent to how model selection
|
|
41
|
+
works and is the correct scope; the RateLimitSentinel covers the in-flight
|
|
42
|
+
throttle messaging separately. Also: if usage can't be read, no swap happens
|
|
43
|
+
(fail-safe) — a genuinely-exhausted agent with an unreadable rollout would not
|
|
44
|
+
swap. Acceptable: it degrades to today's behavior (launch on the main model).
|
|
45
|
+
|
|
46
|
+
## 3. Level-of-abstraction fit
|
|
47
|
+
|
|
48
|
+
Right layer. The decision lives in the openai-codex observability namespace
|
|
49
|
+
(beside the reader it consumes) as a pure function; the WIRING lives in
|
|
50
|
+
SessionManager at the single point where the launch model is finalized for each
|
|
51
|
+
spawn path. It does not belong in the reader (that's a pure read surface) nor in
|
|
52
|
+
a separate poller (a poller would have to persist an override and re-apply it —
|
|
53
|
+
more state, more drift; deciding at launch is stateless and exact).
|
|
54
|
+
|
|
55
|
+
## 4. Signal vs authority compliance
|
|
56
|
+
|
|
57
|
+
**Compliant.** Pure signal-driven decision, zero blocking authority (ref
|
|
58
|
+
`docs/signal-vs-authority.md`). It consumes the authoritative usage signal and
|
|
59
|
+
selects a model. No brittle gate, no veto. Fails safe (read failure → requested
|
|
60
|
+
model). The signal producer (the reader) and this consumer are cleanly split.
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- **Shadowing / double-fire:** none. It's the only thing that selects the codex
|
|
65
|
+
launch model; it runs once per spawn. The two call sites are mutually
|
|
66
|
+
exclusive (headless vs interactive spawn of a given session).
|
|
67
|
+
- **RateLimitSentinel:** complementary, not conflicting. The sentinel handles an
|
|
68
|
+
IN-FLIGHT throttle (backoff + notify + verify recovery); this picks the model
|
|
69
|
+
for the NEXT launch. They operate at different moments and don't race.
|
|
70
|
+
- **`/local-model` + `frameworkDefaultModels`:** the swap takes `options.model`
|
|
71
|
+
/ `defaultModel` (already resolved from those) as its INPUT and only overrides
|
|
72
|
+
when armed + exhausted, so an explicit per-call/local model still flows through
|
|
73
|
+
untouched when the feature is off (default) or the window is healthy.
|
|
74
|
+
- **Shared `~/.codex/config.toml`:** unaffected — the model flows via the launch
|
|
75
|
+
flag (`-c model=` / `--model`), the per-spawn override that already wins over
|
|
76
|
+
the shared config (same mechanism as `codexThreadlineMcpFlags`). No multi-agent
|
|
77
|
+
config collision.
|
|
78
|
+
- **Spawn-path latency:** when DISABLED (default) the fast-path guards return
|
|
79
|
+
before any disk read — zero added latency. When enabled, the read is the same
|
|
80
|
+
bounded path the shipped `/codex/usage` route uses: a recursive walk of
|
|
81
|
+
`$CODEX_HOME/sessions` (`listAllRollouts`, capped to the newest few) plus a
|
|
82
|
+
tail-read of the newest rollout. All async fs, wrapped best-effort (never
|
|
83
|
+
throws, never blocks the spawn). Only runs when the feature is armed.
|
|
84
|
+
|
|
85
|
+
## 6. External surfaces
|
|
86
|
+
|
|
87
|
+
- **Behavioral, opt-in:** when an operator enables it with a verified
|
|
88
|
+
`fallbackModel`, a codex session may launch on a different model than the
|
|
89
|
+
configured default once the weekly window is low. That is the intended,
|
|
90
|
+
operator-armed behavior. A `[SessionManager]` log line records every swap with
|
|
91
|
+
the reason.
|
|
92
|
+
- No new HTTP surface, no new message, no change visible to other agents.
|
|
93
|
+
- Off by default → no change for any existing agent on update.
|
|
94
|
+
|
|
95
|
+
## 7. Rollback cost
|
|
96
|
+
|
|
97
|
+
Low. Pure additive + dark. Back-out = revert the policy module + the
|
|
98
|
+
SessionManager helper/wiring + tests; no data migration, no agent-state repair.
|
|
99
|
+
In the field, neutralise instantly by not setting (or removing)
|
|
100
|
+
`codex.rateLimitModelSwap.enabled` — no redeploy of code required.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Phase 5 — Second-pass reviewer verdict
|
|
105
|
+
|
|
106
|
+
**Concur with the review.** An independent reviewer subagent audited this artifact
|
|
107
|
+
AND the actual code (not just the claims), verifying each risk with file:line
|
|
108
|
+
citations:
|
|
109
|
+
|
|
110
|
+
- **Never blocks/delays/breaks a launch:** the single new
|
|
111
|
+
`await this.resolveCodexLaunchModel(...)` on each spawn path delegates to
|
|
112
|
+
`resolveCodexLaunchModelWithUsage`, whose disk read is `try/catch`-wrapped and
|
|
113
|
+
returns no-swap on any throw (`codexModelSwapPolicy.ts:125-129`); the helper
|
|
114
|
+
always returns `string | undefined`, never throws (`SessionManager.ts`).
|
|
115
|
+
- **Zero disk I/O when disabled:** the three fast-path guards (non-codex /
|
|
116
|
+
disabled-or-no-fallback / already-on-fallback) all return BEFORE the
|
|
117
|
+
`read(...)` call; unit tests assert `read===0` for each.
|
|
118
|
+
- **Disabled/default path is byte-identical:** on every fast-path the helper
|
|
119
|
+
returns the requested model unchanged, so the builder input is unchanged for
|
|
120
|
+
non-codex agents and codex agents with no config block.
|
|
121
|
+
- **Swap-decision correctness:** swaps only on the documented conjunction; uses
|
|
122
|
+
the weekly (`secondary`) window not `primary`; `<=` threshold off-by-one
|
|
123
|
+
covered both ways; never swaps to an empty model.
|
|
124
|
+
- **Both spawn paths covered**, enforced by source-assertion tests.
|
|
125
|
+
- **No conflict** with RateLimitSentinel (different moment), the shared
|
|
126
|
+
`~/.codex/config.toml` (per-spawn flag override wins), or `/local-model`
|
|
127
|
+
(its model is the helper's input, passes through when off).
|
|
128
|
+
|
|
129
|
+
Reviewer notes addressed: §5's spawn-path-latency wording was corrected to
|
|
130
|
+
acknowledge the `listAllRollouts` directory walk (not just a single tail-read).
|
|
131
|
+
One non-blocking nit noted for a future graduation-out-of-dark: the config is
|
|
132
|
+
read via an inline cast rather than a typed `codex?` field on
|
|
133
|
+
`SessionManagerConfig` — intentional to keep the feature dark without a schema
|
|
134
|
+
change; can be promoted to a typed field if/when it ships on by default.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Side-Effects Review — jsonlExists resolves codex rollout files
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `codex-resume-jsonl-exists`
|
|
4
|
+
**Date:** `2026-05-30`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
**Second-pass reviewer:** `instar-echo second-pass checklist`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`ThreadResumeMap.jsonlExists` and `TopicResumeMap.jsonlExists` decided a saved
|
|
11
|
+
session UUID was missing unless a Claude flat-layout transcript
|
|
12
|
+
(`~/.claude/projects/<encoded>/<uuid>.jsonl`) existed. Codex writes rollout files
|
|
13
|
+
at `$CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl`, so both
|
|
14
|
+
predicates returned `false` for every codex session — `get()` returned null and
|
|
15
|
+
resume broke fleet-wide for codex agents (~9 consumers).
|
|
16
|
+
|
|
17
|
+
The change adds `findRolloutFileSync` to the openai-codex `sessionPaths` helper
|
|
18
|
+
(a synchronous sibling of the existing async `findRolloutFile`, since the resume
|
|
19
|
+
guards are synchronous) and makes both `jsonlExists` predicates check the Claude
|
|
20
|
+
layout first (unchanged, early-return on hit) then fall back to the codex layout.
|
|
21
|
+
Returns true if either exists. Reuses the codex session-path seam `TokenLedger`
|
|
22
|
+
already uses.
|
|
23
|
+
|
|
24
|
+
## Decision-point inventory
|
|
25
|
+
|
|
26
|
+
- `findRolloutFileSync` — add — sync codex rollout lookup; chooses whether a
|
|
27
|
+
codex thread has an on-disk rollout.
|
|
28
|
+
- `ThreadResumeMap.jsonlExists` — modify — now resolves codex rollouts in
|
|
29
|
+
addition to the Claude layout.
|
|
30
|
+
- `TopicResumeMap.jsonlExists` — modify — same codex fallback.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Over-block
|
|
35
|
+
|
|
36
|
+
A false "missing" would now require BOTH the Claude layout and the codex layout
|
|
37
|
+
to lack the uuid. The change can only make `jsonlExists` return true in MORE
|
|
38
|
+
cases than before (it adds a second place to look, never removes the first), so
|
|
39
|
+
it cannot newly over-block a session that previously resolved. The only residual
|
|
40
|
+
over-block is the pre-existing one: a genuinely-deleted transcript correctly
|
|
41
|
+
reads as missing in both layouts.
|
|
42
|
+
|
|
43
|
+
## 2. Under-block
|
|
44
|
+
|
|
45
|
+
The codex fallback could in principle resolve a uuid collision — a rollout file
|
|
46
|
+
whose name contains the uuid substring but belongs to a different session. This
|
|
47
|
+
mirrors the existing async `findRolloutFile` matching (`rollout-*-<uuid>.jsonl`)
|
|
48
|
+
and uuids are effectively unique, so the risk is the same as the established
|
|
49
|
+
codex path. The match also requires the `rollout-` prefix and `.jsonl` suffix, so
|
|
50
|
+
an unrelated file merely containing the uuid is not matched (covered by test).
|
|
51
|
+
|
|
52
|
+
## 3. Level-of-abstraction fit
|
|
53
|
+
|
|
54
|
+
The sync lookup lives in the openai-codex `sessionPaths` module, alongside the
|
|
55
|
+
async `findRolloutFile` it mirrors — the one place that owns the codex on-disk
|
|
56
|
+
session layout. The resume predicates consume it rather than reimplementing the
|
|
57
|
+
walk, so the codex layout knowledge stays in exactly one module. No framework
|
|
58
|
+
state is threaded into the predicate; it simply consults both layouts.
|
|
59
|
+
|
|
60
|
+
## 4. Signal vs authority compliance
|
|
61
|
+
|
|
62
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
63
|
+
|
|
64
|
+
- [x] No LLM judgment. This is a deterministic filesystem existence check feeding
|
|
65
|
+
an existing guard. It exercises no new authority — it only makes the existing
|
|
66
|
+
"does this transcript exist" predicate correct for codex agents. Its single
|
|
67
|
+
effect is to let a codex session resume that previously could not; it never
|
|
68
|
+
blocks, kills, or messages.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Side-Effects Review — Codex usage over HTTP (GET /codex/usage)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `codex-usage-visibility`
|
|
4
|
+
**Date:** 2026-05-30
|
|
5
|
+
**Author:** Echo (instar-dev agent)
|
|
6
|
+
**Second-pass reviewer:** not required (no block/allow/lifecycle decision surface)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds a read-only way to see codex account rate-limit usage (the codex `/status`
|
|
11
|
+
windows) without the interactive TUI. A new reader
|
|
12
|
+
(`src/providers/adapters/openai-codex/observability/codexRateLimitReader.ts`)
|
|
13
|
+
finds the newest codex rollout, tail-reads its freshest `token_count`
|
|
14
|
+
`rate_limits`, and returns a structured snapshot. A new route
|
|
15
|
+
(`src/server/routes.ts` → `GET /codex/usage`) surfaces it. Discoverability +
|
|
16
|
+
awareness via `src/server/CapabilityIndex.ts` (capabilities entry),
|
|
17
|
+
`src/scaffold/templates.ts` (new-agent CLAUDE.md section), and
|
|
18
|
+
`src/core/PostUpdateMigrator.ts` (existing-agent back-fill). Also registers two
|
|
19
|
+
prior dark/operational migrator sections in
|
|
20
|
+
`tests/unit/feature-delivery-completeness.test.ts` that were untracked
|
|
21
|
+
(pre-existing red on main). No runtime decision point is touched.
|
|
22
|
+
|
|
23
|
+
## Decision-point inventory
|
|
24
|
+
|
|
25
|
+
- No decision point. The reader returns data; the route returns it over HTTP.
|
|
26
|
+
Nothing is gated, blocked, filtered, or routed based on the result. The route
|
|
27
|
+
is read-only (GET) and Bearer-gated by the existing auth middleware (not new
|
|
28
|
+
logic).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
**No block/allow surface — over-block not applicable.** The route is a read-only
|
|
35
|
+
GET that returns 200 with either the snapshot or `available:false`. It rejects
|
|
36
|
+
nothing.
|
|
37
|
+
|
|
38
|
+
## 2. Under-block
|
|
39
|
+
|
|
40
|
+
**No block/allow surface — under-block not applicable.** There is no failure
|
|
41
|
+
mode the change is meant to catch-and-stop; it only reports.
|
|
42
|
+
|
|
43
|
+
The closest "miss" is data freshness: the snapshot reflects the last
|
|
44
|
+
`token_count` codex wrote, which can lag the true account state by up to one
|
|
45
|
+
codex turn. This is acceptable and documented — it is the same data the `/status`
|
|
46
|
+
screen shows, which is also turn-stamped. Callers that need to react to a hard
|
|
47
|
+
exhaustion should treat `rateLimitReachedType` as the authoritative "we are
|
|
48
|
+
limited now" flag (codex sets it when a window is actually hit).
|
|
49
|
+
|
|
50
|
+
## 3. Level-of-abstraction fit
|
|
51
|
+
|
|
52
|
+
Correct layer. It sits in the openai-codex observability namespace alongside
|
|
53
|
+
`sessionPaths`/`usageMeterProvider` (it reuses `listAllRollouts`), and is exposed
|
|
54
|
+
through the same HTTP routes layer as the other read-only observability surfaces
|
|
55
|
+
(`/tokens/*`, `/sessions/reap-log`). It does NOT belong in the
|
|
56
|
+
`UsageMeterProvider` interface, because that interface's `read()` is a generic
|
|
57
|
+
token-accounting contract and this is codex-specific authoritative rate-limit
|
|
58
|
+
data — conflating them would muddy the `isAuthoritative()` semantics (the meter
|
|
59
|
+
is correctly non-authoritative; these windows ARE authoritative). A future
|
|
60
|
+
refactor could expose this through a dedicated authoritative-windows method, but
|
|
61
|
+
the standalone reader is the right minimal surface now.
|
|
62
|
+
|
|
63
|
+
## 4. Signal vs authority compliance
|
|
64
|
+
|
|
65
|
+
**Compliant.** This is a pure signal producer with zero blocking authority (ref
|
|
66
|
+
`docs/signal-vs-authority.md`). It reads on-disk data and returns it. The
|
|
67
|
+
model-swap policy that will *consume* this signal is a separate change; keeping
|
|
68
|
+
the reader authority-free is exactly the prescribed split (brittle/cheap signal
|
|
69
|
+
at the edge, decisions made by a downstream consumer).
|
|
70
|
+
|
|
71
|
+
## 5. Interactions
|
|
72
|
+
|
|
73
|
+
- **Shadowing:** none. No other route serves `/codex/usage`; no existing reader
|
|
74
|
+
parses `rate_limits`. It reuses `listAllRollouts` (read-only) but does not
|
|
75
|
+
change it.
|
|
76
|
+
- **Double-fire / race:** none. Stateless per-request disk read; no writes, no
|
|
77
|
+
locks, no shared mutable state. Concurrent requests each open + close their own
|
|
78
|
+
file handles.
|
|
79
|
+
- **Adjacent cleanup:** the codex rollout files it reads are owned/rotated by the
|
|
80
|
+
codex CLI; this reader never deletes or mutates them, so it cannot race
|
|
81
|
+
rollout rotation destructively (a rotated-away file just yields the next
|
|
82
|
+
newest, or `null`).
|
|
83
|
+
- **Capabilities lint:** the new `/codex` prefix is now classified in
|
|
84
|
+
`CapabilityIndex`, satisfying the capabilities-discoverability lint (verified
|
|
85
|
+
green).
|
|
86
|
+
|
|
87
|
+
## 6. External surfaces
|
|
88
|
+
|
|
89
|
+
- **New HTTP route** visible to any Bearer-authenticated caller: `GET
|
|
90
|
+
/codex/usage`. It exposes the account's codex rate-limit percentages, reset
|
|
91
|
+
times, plan type, and active model — the same information the account owner
|
|
92
|
+
already sees on the codex `/status` screen. No secrets, tokens, or message
|
|
93
|
+
content are exposed.
|
|
94
|
+
- **CLAUDE.md** gains a "Codex Usage" section for new agents (templates.ts) and
|
|
95
|
+
existing agents (migrator back-fill, idempotent content-sniff). This changes
|
|
96
|
+
agent-visible instructions, handled per Migration Parity.
|
|
97
|
+
- **Timing dependence:** only freshness (see §2) — bounded by codex's per-turn
|
|
98
|
+
write cadence. No dependence on conversation state or uncontrolled runtime
|
|
99
|
+
conditions.
|
|
100
|
+
|
|
101
|
+
## 7. Rollback cost
|
|
102
|
+
|
|
103
|
+
Low. Pure additive feature. Back-out = revert the route + reader + the
|
|
104
|
+
CapabilityIndex/template/migrator/test entries. No data migration, no agent-state
|
|
105
|
+
repair, no release coordination beyond a normal patch. The migrator block is
|
|
106
|
+
idempotent (content-sniffed on `/codex/usage`), so partial application or re-run
|
|
107
|
+
is safe.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Side-Effects Review — ship-gate sha-mismatch error becomes self-service
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `ship-gate-sha-mismatch-error`
|
|
4
|
+
**Date:** `2026-05-30`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
**Second-pass reviewer:** `instar-echo second-pass checklist`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`scripts/instar-dev-precommit.js` blocks a commit when the staged side-effects
|
|
11
|
+
artifact's sha256 differs from the sha recorded in the trace. The old message
|
|
12
|
+
("artifact content has changed ... sha mismatch") never printed the correct sha,
|
|
13
|
+
so authors regenerated the artifact — whose volatile `Date:` field changed the
|
|
14
|
+
bytes and thus the sha — and chased the hash forever (a ~2h grind 2026-05-30,
|
|
15
|
+
worst for codex agents). The hook already computes the correct sha to do the
|
|
16
|
+
comparison; it just was not showing it. The change prints the recorded sha
|
|
17
|
+
(truncated), the EXACT computed sha to write, and the freeze/re-stage/no-amend
|
|
18
|
+
recipe. Failure-path text only; pass/fail logic unchanged.
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
- sha-mismatch error string in `instar-dev-precommit.js` — modify — turns a dead
|
|
23
|
+
end into an actionable self-service fix; does not change which commits block.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Over-block
|
|
28
|
+
|
|
29
|
+
None changed. The gate blocks exactly the same set of commits (a mismatching
|
|
30
|
+
sha). No commit that passed before now fails, and none that failed now passes —
|
|
31
|
+
the comparison is byte-identical. Only the message emitted on an existing block
|
|
32
|
+
changes.
|
|
33
|
+
|
|
34
|
+
## 2. Under-block
|
|
35
|
+
|
|
36
|
+
None changed. The change cannot let a genuinely-mismatched artifact through; the
|
|
37
|
+
`continue` on mismatch is unchanged, so a bad bundle still fails to produce a
|
|
38
|
+
valid trace and the commit is still blocked. Printing the computed sha does not
|
|
39
|
+
weaken the check — the author still has to actually set it and re-stage.
|
|
40
|
+
|
|
41
|
+
## 3. Level-of-abstraction fit
|
|
42
|
+
|
|
43
|
+
The message lives exactly where the mismatch is detected, in the pre-commit
|
|
44
|
+
validator, beside the sha computation it already performs. No new module, no new
|
|
45
|
+
data flow — the fix uses a value the function already holds.
|
|
46
|
+
|
|
47
|
+
## 4. Signal vs authority compliance
|
|
48
|
+
|
|
49
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
50
|
+
|
|
51
|
+
- [x] No LLM, no new authority. This is a developer-experience improvement to an
|
|
52
|
+
existing deterministic gate's error output. The gate's blocking authority is
|
|
53
|
+
unchanged; the change only makes its existing refusal explainable and fixable.
|
|
54
|
+
The printed sha is a checksum of a public review artifact (not sensitive).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Side-Effects Review — Content-hash dedup for the relay-agent ingress
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `threadline-relay-content-dedup`
|
|
4
|
+
**Date:** `2026-05-30`
|
|
5
|
+
**Author:** `instar-echo`
|
|
6
|
+
**Second-pass reviewer:** `instar-echo second-pass checklist`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
This change stops duplicate agent-to-agent replies. The handler at
|
|
11
|
+
`POST /messages/relay-agent` accepts an inbound message, then spawns or resumes
|
|
12
|
+
the receiving agent's session before responding. When that spawn is slow (a
|
|
13
|
+
loaded box), the sender times out and retries the same message with a fresh
|
|
14
|
+
`message.id`. The existing dedup in `MessageRouter.relay` keys on `message.id`,
|
|
15
|
+
so the retry slips through and the receiver spawns/replies twice (observed six
|
|
16
|
+
times in one evening, 2026-05-30).
|
|
17
|
+
|
|
18
|
+
A new module `src/messaging/relayContentDedup.ts` (`RelayContentDedup`) provides
|
|
19
|
+
a bounded, in-memory, fixed-window dedup keyed on the stable triple
|
|
20
|
+
`(senderAgent, threadId, normalized content)`. `src/server/routes.ts` creates
|
|
21
|
+
one instance per server inside `createRoutes` and checks it in the relay-agent
|
|
22
|
+
handler, after envelope validation and before the relay accept. A duplicate
|
|
23
|
+
within the window short-circuits with an idempotent `200`
|
|
24
|
+
(`{ ok: true, deduped: true }`) and no spawn. Tests cover the unit logic (both
|
|
25
|
+
sides of every boundary) and the real HTTP route on a booted `AgentServer`.
|
|
26
|
+
|
|
27
|
+
This ships the dedup alone. The accept-boundary change (respond on inbox-accept
|
|
28
|
+
rather than after the spawn) is the root fix but touches response semantics and
|
|
29
|
+
the warrants-reply gate in the same handler, so it is a separately-reviewed
|
|
30
|
+
follow-up.
|
|
31
|
+
|
|
32
|
+
## Decision-point inventory
|
|
33
|
+
|
|
34
|
+
- `RelayContentDedup` — add — the dedup engine; chooses whether an inbound relay
|
|
35
|
+
message is a fresh send or a duplicate retry.
|
|
36
|
+
- `relayContentDedup` instance in `createRoutes` — add — per-server-process
|
|
37
|
+
dedup state, scoped to one server instance for test isolation.
|
|
38
|
+
- relay-agent handler guard — add — runs the dedup check on the ingress path and
|
|
39
|
+
short-circuits duplicates before the relay accept and the session spawn.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Over-block
|
|
44
|
+
|
|
45
|
+
The guard drops an inbound only when the exact same sender, thread, and content
|
|
46
|
+
recur within the window (default 60 s). The realistic over-block is a legitimate
|
|
47
|
+
rapid identical resend — an agent sending the same short text (for example a bare
|
|
48
|
+
acknowledgement) twice in the same thread within a minute would have the second
|
|
49
|
+
collapsed. The window is intentionally short and the match is exact, so this is
|
|
50
|
+
narrow; identical back-to-back content on one thread is far more likely a retry
|
|
51
|
+
than two distinct intents.
|
|
52
|
+
|
|
53
|
+
A subtler case: the guard records a key when it passes the check, before the
|
|
54
|
+
downstream spawn outcome is known. If the first attempt's spawn genuinely failed
|
|
55
|
+
and the sender resent identical content to retry it, that resend would be
|
|
56
|
+
dropped. In practice the retry trigger is a sender timeout while the original
|
|
57
|
+
spawn is still in flight (so the retry is genuinely redundant), not a spawn
|
|
58
|
+
failure — a failed spawn already returns `200` today, so it does not provoke a
|
|
59
|
+
retry. The limitation is documented rather than papered over.
|
|
60
|
+
|
|
61
|
+
## 2. Under-block
|
|
62
|
+
|
|
63
|
+
A retry whose content was modified (for example the sender appends text) hashes
|
|
64
|
+
differently and is not collapsed — correct, it is a different message. Messages
|
|
65
|
+
missing a `threadId` or body content are passed through unchanged, because there
|
|
66
|
+
is no stable key for them and they are not part of the observed failure.
|
|
67
|
+
|
|
68
|
+
The dedup is per-server-process in-memory state. Across a multi-machine failover
|
|
69
|
+
the new holder starts with an empty window, so a duplicate that straddles the
|
|
70
|
+
handoff could slip through. This is acceptable: the durable per-message ledger
|
|
71
|
+
and the `message.id` relay dedup still apply, and the straddle window is seconds
|
|
72
|
+
wide.
|
|
73
|
+
|
|
74
|
+
## 3. Level-of-abstraction fit
|
|
75
|
+
|
|
76
|
+
The dedup lives at the relay-agent ingress as a small local module, not inside
|
|
77
|
+
`MessageRouter.relay`. The relay funnel's dedup is the `message.id` identity
|
|
78
|
+
layer with redelivery semantics; the content-hash layer is a distinct identity
|
|
79
|
+
(what the message *is*, not its id) for the specific retry-with-fresh-id failure.
|
|
80
|
+
Keeping it a separate module checked at the route keeps the change local to where
|
|
81
|
+
the failure manifests and leaves the relay funnel's contract unchanged.
|
|
82
|
+
|
|
83
|
+
## 4. Signal vs authority compliance
|
|
84
|
+
|
|
85
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
86
|
+
|
|
87
|
+
- [x] No LLM judgment is involved. This is a deterministic mechanical guard — an
|
|
88
|
+
exact content-hash match within a fixed time window — exercising the same kind
|
|
89
|
+
of narrow, idempotent authority the existing `message.id` relay dedup already
|
|
90
|
+
holds. It never interprets meaning; it only recognizes an exact repeat. A
|
|
91
|
+
false match requires identical sender, thread, and bytes within seconds, and
|
|
92
|
+
its only effect is to withhold a second spawn (no data is lost; the message is
|
|
93
|
+
still acknowledged with `200`).
|