feat: curator prompt + gallery pipeline#117
Conversation
Replace the terse dashboard-classifier prompt with the exhibit curator: the
model now outputs { pulse, galleryOps } over the seven-type exhibit vocabulary,
receives THE GALLERY back as memory, and curates (create/refresh/retire) over
completeness. Prompt text installed verbatim from docs/plans/curator-prompt-v2.md
(brought into the branch alongside). Doc comment rewritten to the curator design
with a 2026-07-23 iteration-log milestone pointing at plan-exhibit-floor.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
createGalleryStore holds the current ExhibitArtifact map and applies curator ops: create (fresh, stamps createdAtEvent), refresh (replace by id, keep createdAtEvent, re-stamp refreshedAtEvent, back to active), retire (kept with reason for the archive shelf). Surviving fresh exhibits graduate to active on the next packet; staleness is dual-gated by decayClass (fast/medium/slow → stale after N events OR M minutes since refresh, documented constants). The store is the curator's memory: getActive/getRetiredSummaries feed the next packet, getArtifacts drives the renderer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
parseCuratorResponse handles three shapes so provider/prompt mismatches degrade
gracefully: the curator (v2) { pulse, galleryOps }, the legacy flat (v1) shape,
and unparseable output. validateExhibitArtifact checks each artifact against the
discriminated union in src/renderer/exhibits/types.ts — envelope fields,
exhibitType membership, and per-type payload required fields (structural, not
exhaustive); invalid artifacts are dropped with a logged reason, never rendered
half-formed. The legacy ShadowInsight kinds are derived FROM the new shape
(pulse.phase→phase, pulse.riskLevel→risk, pulse.headline→objective, created
artifact title+narrative→summary, momentum next[0]→next_move) so the status
strip, vignette, ghost trail, and ShadowPanel keep working unchanged.
parseModelResponse is retained, delegating to the curator parse.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
The packager serializes each active/stale exhibit (envelope + one-line payload summary: beats+thread names for narratives, node count + focus for DAGs, etc.) and includes retired-this-session ids+reasons one line each so the model does not recreate what it retired. The gallery section is capped at ~15% of the packet token budget: over that, payload summaries are dropped (envelopes only), then the least-relevant exhibits are pruned until it fits. buildContextPacket takes an optional gallery context; buildUserMessage renders THE GALLERY and Retired sections (sanitized like the other transcript-derived sections). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
The inference engine holds a GalleryStore: each run feeds the current gallery (active/stale + retired ids) into the packet as the curator's memory, then applies the parsed ops and exposes the resulting gallery via a new onGallery callback. start-main-process forwards it to the session manager's setGallery, which rides it on the next snapshot (SnapshotPayload.gallery) via the existing dirty-refresh path. App passes snapshot.gallery to ExhibitStage; the boot/ fixture snapshot leaves it undefined so the stage keeps its hand-authored fixture gallery, while live/replay snapshots show the curator's floor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
Fewer, bigger calls per the exhibit-floor design: keep the immediate paths (agent_completed, tool_failed) but raise the normal-path floor to 25 events / 120s (was 10 / 30s). maxEventsBetween stays 50 as the hard ceiling. New tests pin the default thresholds via the injectable virtual clock so a silent loosening fails; existing trigger tests use explicit configs and are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
The replay runner now owns a GalleryStore too: live-mode firings parse curator responses, apply ops, and feed the accumulated gallery back into each packet. Each TriggerRecord gains a per-firing galleryOps summary (create/refresh/retire counts + affected ids), and the report carries the final gallery. A new --gallery-out flag serializes that gallery to JSON; the CLI also prints an exhibit-count summary line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| // Feed the accumulated gallery back in as the curator's memory. | ||
| galleryStore.refreshStaleness(snapshot.length); | ||
| const packet = buildContextPacket(state, snapshot, { | ||
| gallery: galleryStore.getActive(), |
There was a problem hiding this comment.
Suggestion: Refreshing staleness without passing replay virtual time falls back to Date.now(), making gallery aging depend on wall-clock runtime speed instead of virtual replay time. This breaks determinism across replay speeds and can mark artifacts stale/active inconsistently; pass clock.now() so staleness is tied to virtual timeline. [logic error]
Severity Level: Critical 🚨
- ❌ Replay gallery staleness depends on wall-clock execution speed.
- ⚠️ Same transcript yields inconsistent exhibit statuses across runs.Steps of Reproduction ✅
1. Start a deterministic replay by calling `runReplay(options)` in
`src/replay/replay-runner.ts` with `options.infer = 'live'`. The runner constructs a
virtual clock via `const clock = createVirtualClock(...)` at `src/replay/replay-runner.ts`
BulkRead line 27 and uses that clock throughout to decouple replay time from wall-clock
time (see `src/shared/clock.ts` lines 77-149 for the `VirtualClock` implementation).
2. When a trigger fires, `startLiveInference(record)` (defined around BulkRead lines 67-85
in `src/replay/replay-runner.ts`) takes a snapshot: `const snapshot = released.slice();`
and `const state = deriveState(snapshot, title);` (BulkRead lines 70-72). Immediately
before building the context packet, it calls
`galleryStore.refreshStaleness(snapshot.length);` (PR diff line 504, matching BulkRead
line 73 in `src/replay/replay-runner.ts`). This call only passes the event index and omits
any time argument.
3. In `src/inference/gallery-store.ts`, the `GalleryStore` interface declares
`refreshStaleness(atEvent: number, nowMs?: number): void;` at line 91, and the
implementation uses a default wall-clock time: `refreshStaleness(atEvent, nowMs =
Date.now())` at line 239, internally delegating to the helper at lines 127-143 that
computes `minuteAge = (nowMs - refreshedAtMs) / 60_000` (lines 103-112) and staleness
thresholds `STALENESS_THRESHOLDS` at lines 59-63. Because `runReplay` never passes
`clock.now()` into `refreshStaleness`, the gallery’s minute-based staleness is driven by
`Date.now()` instead of the virtual replay timeline.
4. As a result, running the same transcript twice with different replay speeds or on
machines with different performance will produce different staleness behavior: exhibits
will flip between `active` and `stale` at different points even though virtual time is
identical. This non-determinism can be observed by inspecting the final gallery returned
in the `ReplayReport.gallery` field (set from `galleryStore.getArtifacts()` at
`src/replay/replay-runner.ts` PR diff line 675) or the optional `galleryOutPath` JSON
output at lines 690-692, and comparing runs with different `options.speed` values – the
wall-clock based staleness in `gallery-store.ts` causes inconsistent exhibit statuses
across replay speeds.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/replay/replay-runner.ts
**Line:** 504:504
**Comment:**
*Logic Error: Refreshing staleness without passing replay virtual time falls back to `Date.now()`, making gallery aging depend on wall-clock runtime speed instead of virtual replay time. This breaks determinism across replay speeds and can mark artifacts stale/active inconsistently; pass `clock.now()` so staleness is tied to virtual timeline.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| .then((response) => { | ||
| const insights = parseModelResponse(response.text); | ||
| const { insights, galleryOps } = parseCuratorResponse(response.text); | ||
| const applied = galleryStore.applyOps(galleryOps, released.length); |
There was a problem hiding this comment.
Suggestion: The gallery ops are being applied with the current released.length when the response lands, but the response was generated from the earlier dispatched snapshot. This stamps artifacts with a later event index than the model actually saw, which skews lifecycle transitions (fresh/active/stale) and can mis-age artifacts under inference latency. Apply ops using the dispatch-time event index captured before the infer call. [incorrect variable usage]
Severity Level: Major ⚠️
- ⚠️ Replay gallery artifact aging misaligned with curator’s event view.
- ⚠️ Staleness thresholds skewed by extra events during inference.Steps of Reproduction ✅
1. Start a replay in live inference mode by calling `runReplay(options)` in
`src/replay/replay-runner.ts` with `options.infer = 'live'` and a transcript that produces
many `CanonicalEvent`s (see `runReplay` body around lines 1-120 and 430-549 in
`src/replay/replay-runner.ts`).
2. Observe that when a trigger fires, `startLiveInference(record)` is invoked (defined in
`src/replay/replay-runner.ts` around lines 67-85 from the BulkRead output). Inside it, a
snapshot of events is taken via `const snapshot = released.slice();` and `const state =
deriveState(snapshot, title);` (file `src/replay/replay-runner.ts`, BulkRead lines 70-72),
and `const dispatchEventIndex = released.length;` is captured just before dispatch
(BulkRead line 82). This `dispatchEventIndex` represents the number of events the curator
model actually sees for this request.
3. While the inference call `activeClient.infer(request)` is in flight (see
`src/replay/replay-runner.ts` BulkRead lines 85-87, with `inflight = true` set at line
69), the main replay loop continues to release additional events into `released[]`
(earlier in `runReplay`, not shown in the hunk but evidenced by `record.eventsBehind =
released.length - dispatchEventIndex;` at BulkRead line 94). This metric only makes sense
if `released.length` can grow between dispatch and response, confirming additional events
are appended while the call is pending.
4. When the response lands, the `.then` handler at `src/replay/replay-runner.ts:517-103`
(BulkRead lines 87-103) executes `const { insights, galleryOps } =
parseCuratorResponse(response.text);` followed by `const applied =
galleryStore.applyOps(galleryOps, released.length);` (PR diff line 518). In
`src/inference/gallery-store.ts` the implementation `applyOps(ops, atEvent, nowMs =
Date.now())` at lines 171-210 stamps artifacts as if they were created/refreshed at
`atEvent` (`createdAtEvent` and `refreshedAtEvent` are set to `atEvent` in `upsert` at
lines 153-162) and recomputes staleness via `refreshStaleness(atEvent, nowMs)` at line
209. Because `atEvent` is passed as the response-time `released.length` rather than the
dispatch-time `dispatchEventIndex`, exhibits are stamped and aged relative to a later
event index than the model actually saw, skewing fresh/active/stale transitions under
non-trivial inference latency and extra events released while the call is in flight.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/replay/replay-runner.ts
**Line:** 518:518
**Comment:**
*Incorrect Variable Usage: The gallery ops are being applied with the current `released.length` when the response lands, but the response was generated from the earlier dispatched snapshot. This stamps artifacts with a later event index than the model actually saw, which skews lifecycle transitions (fresh/active/stale) and can mis-age artifacts under inference latency. Apply ops using the dispatch-time event index captured before the infer call.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
Blocking findings
-
Replay staleness uses wall-clock time instead of replay time. In
src/replay/replay-runner.ts:502and:518,refreshStaleness()andapplyOps()omitnowMs, soGalleryStoredefaults toDate.now(). A multi-hour transcript replayed at--speed 60advances the virtual clock by hours but wall time by seconds; minute-basedfast/medium/slowdecay therefore never fires. The replay packet, gallery report, and--gallery-outresult can keep exhibits active long after their virtual session lifetime. Passclock.now()(or the event timestamp) to both calls. -
No-op curator responses do not update the renderer gallery.
src/inference/shadow-inference-engine.ts:117-119invokesonGalleryonly whengalleryOps.length > 0, even though staleness is recomputed on every inference and the prompt explicitly allows an empty op list. An active exhibit becomingstaleon a no-op response never reachesSessionManager, and an initial/fully-empty gallery cannot replace the fixture fallback. Emit the full gallery after every successful response and distinguish “no inference yet” from “inference produced an empty gallery” inSessionManager. -
Gallery state leaks across capture-session switches.
createGalleryStore()atsrc/inference/shadow-inference-engine.ts:68is scoped to the engine lifetime, whileSessionManager.startSession()resets the event buffer for a new session (src/capture/session-manager.ts:122-125) without resettinglatestGallery; the engine has no session-boundary reset either. After switching transcripts, the previous session’s exhibits remain in THE GALLERY and can be rendered/used as curator memory for the new session. Reset both the store and renderer cache when the buffer session changes. -
Artifact validation is too shallow for an untrusted model-to-renderer boundary.
src/inference/response-parser.ts:205-240verifies only top-level arrays/scalars, not the objects and fields inside those arrays. For example,activity_narrative.threads: [null]reachesActivityNarrative’sthreads.map(t => t.id)and malformed beats can reachbeats[idx - 1].thread; analogous dereferences exist in the other exhibit components. A response that is “array-shaped” but malformed can throw during render and take down the exhibit surface. Validate nested payload entries (including enum/range constraints and cross-references) or add a defensive rendering boundary before accepting an artifact.
Additional contract gap
src/renderer/App.tsx:509 now passes snapshot.gallery, but the openReplayFile() path still builds snapshots through src/electron/session-io.ts without loading a gallery. The CLI replay report/--gallery-out is not consumed by the renderer, so opening a replay continues to show the fixture gallery rather than the curator floor. If replay UI is part of PR-C’s stated live/replay wiring, this path still needs to be connected.
The momentum contract also disagrees on confidence units: the prompt specifies 0.0–1.0, while src/renderer/exhibits/types.ts/Momentum.tsx render a 0–100 percentage and the parser leaves the artifact payload unchanged. A model-compliant 0.88 will display as 0.88%; align the contract or normalize the payload.
Verification
- Existing GitHub
testcheck for head5c15c1cb6c8de98ff9d1b32dec50c4627d14bc0f: passed. - GitGuardian security check: passed.
git diff --checkagainst based3799eaae35b1e0a55d6fb9a62e9a1006a89236e: clean.- No same-head formal review authored by
CharlieHelps/CharlieCreateswas present before submission.
There was a problem hiding this comment.
Pull request overview
Implements the “curator” (v2) inference contract that returns { pulse, galleryOps }, adds a persistent GalleryStore that’s fed back into subsequent inference packets as “THE GALLERY”, and wires the resulting gallery through live sessions and replay so the ExhibitStage renders the curator-authored exhibit floor.
Changes:
- Added curator response parsing/validation with backward-compatible derivation of legacy
ShadowInsight[]from curator outputs. - Introduced a
GalleryStoreand “THE GALLERY” packet section (with budget-capping behavior) and wired gallery propagation through live engine, session snapshots, and replay/report outputs. - Tuned inference trigger cadence defaults (25 events / 120s) and added targeted test coverage for the new pipeline.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/inference/trigger-thresholds.test.ts | Pins the new default trigger cadence (25 events / 120s) and immediate/ceiling behavior. |
| tests/inference/shadow-inference-engine.test.ts | Adds integration coverage for curator gallery ops → exposed gallery → next packet contains THE GALLERY. |
| tests/inference/gallery-store.test.ts | Covers create/refresh/retire semantics, fresh→active promotion, and staleness behavior. |
| tests/inference/curator-response-parser.test.ts | Verifies curator parsing, invalid-artifact dropping, legacy fallback, and insight derivation. |
| tests/inference/context-packager-gallery.test.ts | Tests gallery serialization, pruning behavior, and prompt rendering of THE GALLERY sections. |
| src/shared/schema.ts | Extends snapshot payload to optionally carry curator gallery artifacts. |
| src/replay/replay-runner.ts | Carries gallery through replay inference calls; records gallery ops and exports final gallery. |
| src/renderer/App.tsx | Passes snapshot gallery into ExhibitStage so live/replay render curated artifacts. |
| src/inference/trigger.ts | Raises default normal-path trigger thresholds and documents curator cadence intent. |
| src/inference/shadow-inference-engine.ts | Applies curator ops via GalleryStore and wires gallery into packet + renderer callback. |
| src/inference/response-parser.ts | Adds curator (v2) parsing, artifact validation, and legacy insight derivation. |
| src/inference/prompts.ts | Replaces system prompt with verbatim curator prompt v2 + updated rationale doc comment. |
| src/inference/prompt-builder.ts | Adds gallery/retiredGallery packet fields and renders THE GALLERY in the user message. |
| src/inference/gallery-store.ts | New GalleryStore implementing create/refresh/retire lifecycle + dual-gated staleness. |
| src/inference/context-packager.ts | Packs THE GALLERY into the context packet with a 15% token-budget cap strategy. |
| src/electron/start-main-process.ts | Wires engine onGallery to SessionManager so renderer snapshots update. |
| src/capture/session-manager.ts | Stores latest gallery and includes it in snapshots (undefined when empty to keep fixture fallback). |
| scripts/replay.ts | Adds --gallery-out CLI flag and prints gallery summary in replay output. |
| docs/plans/curator-prompt-v2.md | Adds the verbatim curator prompt text as a reviewable prose companion. |
Comments suppressed due to low confidence (2)
src/replay/replay-runner.ts:519
galleryStore.applyOps(galleryOps, released.length)also defaults toDate.now()internally, so minute-based staleness and refreshed-at timestamps will vary across replay runs. For byte-deterministic replays, pass the runner’sclock.now()asnowMs.
.then((response) => {
const { insights, galleryOps } = parseCuratorResponse(response.text);
const applied = galleryStore.applyOps(galleryOps, released.length);
const landVirtualMs = clock.now();
src/inference/context-packager.ts:95
packGalleryenforces the 15% cap by stripping payload summaries and then pruning exhibits, but it never capsretiredGallery. If the retired list grows large (or reasons are long), the section can exceed the intended budget even after pruning all exhibits. This can crowd out transcript context and increase token costs.
const cap = Math.floor(tokenBudget * GALLERY_BUDGET_FRACTION);
const byRelevance = [...artifacts].sort((a, b) => b.relevance - a.relevance);
const estimate = (entries: GalleryPacketEntry[]): number => estimateTokens({ gallery: entries, retiredGallery: retired });
// 1. Full entries (envelope + payload summary).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Trigger conditions (any one fires): | ||
| * - At least minEventsBetween (10) new events since last inference | ||
| * - At least timeBetweenMs (30 s) since last inference | ||
| * - At least minEventsBetween (25) new events since last inference | ||
| * - At least timeBetweenMs (120 s) since last inference | ||
| * - maxEventsBetween (50) events forces a trigger regardless of timer | ||
| * - Risk escalation: derived risk level rises to 'medium' or above |
| if (opts.onGallery && galleryOps.length > 0) { | ||
| opts.onGallery(galleryStore.getArtifacts()); | ||
| } |
| // Feed the accumulated gallery back in as the curator's memory. | ||
| galleryStore.refreshStaleness(snapshot.length); | ||
| const packet = buildContextPacket(state, snapshot, { |
| * THE GALLERY competes with transcript detail for packet space. Cap it at 15% | ||
| * of the token budget: over that, drop payload summaries (envelopes only), then | ||
| * drop the least-relevant exhibits until it fits. Retired-line feedback is kept | ||
| * regardless — it is one cheap line each and prevents recreating retired work. | ||
| */ |
| `[${g.exhibitType}] ${g.id} "${sanitize(g.title)}" ` + | ||
| `(relevance ${g.relevance.toFixed(2)}, ${g.status}, ${g.decayClass})` |
There was a problem hiding this comment.
Suggestion: g.id is model-authored data but is interpolated into the next prompt without sanitization, so an id containing control characters/newlines can inject extra lines or sections into THE GALLERY and corrupt subsequent prompt structure. Sanitize or strictly validate ids before embedding them in prompt text. [security]
Severity Level: Major ⚠️
❌ Off-host curator prompt corrupted by malformed exhibit identifiers.
⚠️ THE GALLERY memory misrenders, confusing shadow curator behavior.Steps of Reproduction ✅
1. The Electron main process creates the runtime inference engine via
`createInferenceEngine` in `src/electron/start-main-process.ts:140-40`, wiring `buffer`,
`getState`, `privacy`, and `onInsights`/`onGallery` callbacks.
2. When trigger conditions are met, `runInference` in
`src/inference/shadow-inference-engine.ts:77-107` reads all events from the buffer,
refreshes gallery staleness, and calls `buildContextPacket(state, events, { gallery:
galleryStore.getActive(), retiredGallery: galleryStore.getRetiredSummaries() })`.
3. `buildContextPacket` in `src/inference/context-packager.ts:151-220` calls
`packGallery`, which uses `toEnvelopeEntry` at `src/inference/context-packager.ts:67-79`
to build `GalleryPacketEntry` objects with `id: artifact.id` (no sanitization).
`artifact.id` is model-authored and only structurally validated by
`validateExhibitArtifact` in `src/inference/response-parser.ts:252-52`, which requires a
non-empty string but does not constrain content.
4. `buildUserMessage` in `src/inference/prompt-builder.ts:70-153` renders THE GALLERY
section, iterating `packet.gallery` and emitting `[${g.exhibitType}] ${g.id}
"${sanitize(g.title)}" (relevance ...)` at lines 136-137. Because `g.id` is inserted
unsanitized, a curator response whose artifact `id` contains newlines or control
characters will inject additional lines into THE GALLERY block of the prompt sent via
`InferenceClient.infer`, corrupting the section’s structure while all other text fields
are sanitized.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/prompt-builder.ts
**Line:** 136:137
**Comment:**
*Security: `g.id` is model-authored data but is interpolated into the next prompt without sanitization, so an id containing control characters/newlines can inject extra lines or sections into `THE GALLERY` and corrupt subsequent prompt structure. Sanitize or strictly validate ids before embedding them in prompt text.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| if (retired.length > 0) { | ||
| lines.push('', `--- Retired this session (${retired.length}) ---`); | ||
| for (const r of retired) { | ||
| lines.push(`${r.id}: ${sanitize(r.reason)}`); |
There was a problem hiding this comment.
Suggestion: Retired artifact ids are also emitted unsanitized, so a crafted id can break packet formatting or inject additional lines into the retired section. Apply the same sanitization/validation used for other text fields before writing ids into the prompt. [security]
Severity Level: Major ⚠️
❌ Retired gallery section formatting corrupted by unsafe ids.
⚠️ Model memory feedback about retirements becomes unreliable.Steps of Reproduction ✅
1. Curator responses are parsed in `src/inference/response-parser.ts:212-235`;
`parseCuratorResponse` calls `parseGalleryOps` at `src/inference/response-parser.ts:65-95`
to build `GalleryOp[]` from the model’s `galleryOps` array.
2. For retire operations, `parseGalleryOps` constructs `GalleryOp` objects with `{ op:
'retire', artifactId: entry.artifactId, reason: ... }` when `artifactId` is a non-empty
string (validated by `nonEmptyString`), but does not sanitize or restrict the characters
in `artifactId` (lines 80-89).
3. `galleryStore.applyOps` in `src/inference/gallery-store.ts:171-205` records retirement
reasons keyed by this `artifactId` in the `retiredReasons` map, and `getRetiredSummaries`
at `src/inference/gallery-store.ts:235-237` later exposes them as `{ id, reason }` pairs,
with `id` taken directly from the model-controlled `artifactId`.
4. `buildContextPacket` in `src/inference/context-packager.ts:216-220` passes these
summaries through to the `ShadowContextPacket.retiredGallery` field, which
`buildUserMessage` in `src/inference/prompt-builder.ts:145-151` renders as lines using
``${r.id}: ${sanitize(r.reason)}`` (line 149). Because `r.id` is interpolated without
sanitization while `r.reason` is sanitized, a retired id containing newlines or formatting
tokens will break the retired section’s structure in the next prompt, injecting extra
lines or headings into the “Retired this session” block that is fed back as memory.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/prompt-builder.ts
**Line:** 149:149
**Comment:**
*Security: Retired artifact ids are also emitted unsanitized, so a crafted id can break packet formatting or inject additional lines into the retired section. Apply the same sanitization/validation used for other text fields before writing ids into the prompt.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| if (opts.onGallery && galleryOps.length > 0) { | ||
| opts.onGallery(galleryStore.getArtifacts()); | ||
| } |
There was a problem hiding this comment.
Suggestion: onGallery is only emitted when galleryOps.length > 0, but applyOps can still change gallery state (for example fresh→active promotion and staleness recomputation) even with an empty op list. This leaves the renderer with stale statuses and breaks the contract that gallery updates are published after each curator response. Emit onGallery whenever the gallery state may have changed after applyOps (or unconditionally after each run). [api mismatch]
Severity Level: Critical 🚨
❌ Exhibit floor shows stale statuses after curator responses.
⚠️ Renderer SnapshotPayload.gallery diverges from store state.Steps of Reproduction ✅
1. The Electron main process wires the inference engine in
`src/electron/start-main-process.ts:140-40`, passing an `onGallery` callback that logs the
count and forwards artifacts to `currentSessionManager.setGallery(artifacts)` (lines
32-38).
2. The session manager’s `setGallery` implementation in
`src/capture/session-manager.ts:236-241` stores the latest gallery into `latestGallery`
and marks the IPC bridge dirty so the renderer refreshes its snapshot; `buildSnapshot` at
`src/capture/session-manager.ts:81-113` then includes this gallery as `snapshot.gallery`
when `latestGallery.length > 0`.
3. Each inference run in `src/inference/shadow-inference-engine.ts:77-107` feeds the
current gallery back into the model (`buildContextPacket(state, events, { gallery:
galleryStore.getActive(), retiredGallery: galleryStore.getRetiredSummaries() })`), then
parses the curator response with `parseCuratorResponse`, obtaining `{ insights, galleryOps
}` from `src/inference/response-parser.ts:212-235`.
4. `galleryStore.applyOps` in `src/inference/gallery-store.ts:171-210` is called with
`galleryOps` and the current event index. Even when `galleryOps` is an empty array,
`applyOps` still promotes any surviving `fresh` exhibits to `active` (lines 174-178) and
recomputes staleness via `refreshStaleness(atEvent, nowMs)` (lines 208-209), changing
artifact `status` fields within the store. Because `createInferenceEngine` only invokes
`opts.onGallery` when `galleryOps.length > 0`
(`src/inference/shadow-inference-engine.ts:117-119`), curator responses that return no ops
update gallery state internally but never refresh `snapshot.gallery` in the renderer,
leaving the exhibit floor view out-of-sync with fresh→active promotions and staleness
aging.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/shadow-inference-engine.ts
**Line:** 117:119
**Comment:**
*Api Mismatch: `onGallery` is only emitted when `galleryOps.length > 0`, but `applyOps` can still change gallery state (for example fresh→active promotion and staleness recomputation) even with an empty op list. This leaves the renderer with stale statuses and breaks the contract that gallery updates are published after each curator response. Emit `onGallery` whenever the gallery state may have changed after `applyOps` (or unconditionally after each run).
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| // The curator gallery only rides live/replay snapshots. When empty | ||
| // (no inference yet), leave it undefined so ExhibitStage falls back to | ||
| // its hand-authored fixture gallery rather than rendering an empty floor. | ||
| gallery: latestGallery.length > 0 ? latestGallery : undefined, |
There was a problem hiding this comment.
Suggestion: The gallery state is persisted and always injected into snapshots once non-empty, but it is never cleared on session switches or manager stop. This leaks artifacts from a previous session into the next one until new curator output arrives. Reset gallery state when a new session starts and during teardown. [stale reference]
Severity Level: Major ⚠️
- ❌ New session shows exhibits from previous session gallery.
- ⚠️ Gallery misleads about current session’s state.
- ⚠️ With inference disabled, stale gallery persists indefinitely.Steps of Reproduction ✅
1. Start the Electron main process via `startMainProcess()`
(src/electron/start-main-process.ts:138), which constructs a stable `sessionManager` by
calling `createSessionManager()` (src/capture/session-manager.ts:55) and starts live
capture with `currentSessionManager.start()` (start-main-process.ts:208).
2. Enable off-host inference so the inference engine starts
(shadow-inference-engine.ts:175-185). When the curator returns gallery ops,
`parseCuratorResponse()` yields artifacts that `galleryStore.applyOps()` stores, and
`createInferenceEngine()` calls the `onGallery` callback with
`galleryStore.getArtifacts()` (src/inference/shadow-inference-engine.ts:103-119).
3. The `onGallery` callback in `startMainProcess` logs and forwards artifacts to the
session manager via `currentSessionManager.setGallery(artifacts)`
(src/electron/start-main-process.ts:171-177). In `createSessionManager()`, `setGallery()`
assigns `latestGallery = artifacts` (src/capture/session-manager.ts:73-75,236-241).
`buildSnapshot()` then injects this array into the snapshot using `gallery:
latestGallery.length > 0 ? latestGallery : undefined`
(src/capture/session-manager.ts:102-113).
4. When the capture transport later detects a new session (different `sessionId` or
`path`), `onSessionStarted` calls `startSession(session)`
(src/capture/session-manager.ts:167-185), which updates `buffer` and `activeSession` but
never resets `latestGallery`. Before the curator has produced a new gallery for this
session (or when off-host inference is disabled and no curator runs, see
shadow-inference-engine.ts:175-183), `App` refreshes the live snapshot via
`getLiveSnapshot()` (src/renderer/App.tsx:212-220), which calls
`sessionManager.getCurrentSnapshot()` (src/shared/schema.ts:180-184,
src/capture/session-manager.ts:225-227). The resulting `SnapshotPayload.gallery` still
holds the previous session’s artifacts, and ExhibitStage renders them for the new session
(src/renderer/App.tsx:504-510), demonstrating stale gallery leakage across sessions.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/capture/session-manager.ts
**Line:** 111:111
**Comment:**
*Stale Reference: The gallery state is persisted and always injected into snapshots once non-empty, but it is never cleared on session switches or manager stop. This leaks artifacts from a previous session into the next one until new curator output arrives. Reset gallery state when a new session starts and during teardown.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| ): { gallery: GalleryPacketEntry[]; retiredGallery: Array<{ id: string; reason: string }> } { | ||
| const cap = Math.floor(tokenBudget * GALLERY_BUDGET_FRACTION); | ||
| const byRelevance = [...artifacts].sort((a, b) => b.relevance - a.relevance); | ||
| const estimate = (entries: GalleryPacketEntry[]): number => estimateTokens({ gallery: entries, retiredGallery: retired }); |
There was a problem hiding this comment.
Suggestion: The gallery section cap is computed including retiredGallery, but retired entries are never pruned, so a long session can grow this list unbounded and force oversized packets or starve active gallery content. Apply a hard limit/pruning strategy to retired entries as well. [performance]
Severity Level: Major ⚠️
- ⚠️ Active exhibits dropped from prompt in long sessions.
- ⚠️ Retired summaries dominate gallery section budget.
- ⚠️ Larger gallery section pressures overall context budget.Steps of Reproduction ✅
1. The curator’s gallery store records retire operations via
`createGalleryStore().applyOps()` (src/inference/gallery-store.ts:171-205), storing
retirement reasons in the `retiredReasons` map and exposing them through
`getRetiredSummaries()` (gallery-store.ts:235-237). This map grows over the session and is
never pruned.
2. Both the live inference engine and replay harness build context packets that include
the full retired list: `createInferenceEngine.runInference()` calls
`buildContextPacket(state, events, { gallery: galleryStore.getActive(), retiredGallery:
galleryStore.getRetiredSummaries() })` (src/inference/shadow-inference-engine.ts:87-95),
and `runReplay()` does the same when constructing packets for offline evaluation
(src/replay/replay-runner.ts:503-507).
3. `buildContextPacket()` delegates to `packContext()`, which serializes THE GALLERY
section via `packGallery(options.gallery ?? [], options.retiredGallery ?? [],
tokenBudget)` (src/inference/context-packager.ts:216-220). Inside `packGallery()`, a cap
is computed as `const cap = Math.floor(tokenBudget * GALLERY_BUDGET_FRACTION)` and token
usage is estimated as `estimateTokens({ gallery: entries, retiredGallery: retired })`
(context-packager.ts:91-93).
4. In a long-running session or replay with many retire ops, `getRetiredSummaries()` can
return a large array. `packGallery()` attempts to keep `{ gallery, retiredGallery }` under
the cap by dropping active exhibit entries (context-packager.ts:104-110) but never trims
`retired`. As `retired` grows, the estimator forces removal of active exhibits to fit the
cap, starving current gallery context, and once retired alone exceeds the cap, the
function returns `{ gallery: [], retiredGallery: retired }` (context-packager.ts:111)
despite comments claiming retired lines are “cheap”. This allows retired entries to
dominate gallery budget and, in extreme cases, overshoot the intended 15% section cap.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/context-packager.ts
**Line:** 93:93
**Comment:**
*Performance: The gallery section cap is computed including `retiredGallery`, but retired entries are never pruned, so a long session can grow this list unbounded and force oversized packets or starve active gallery content. Apply a hard limit/pruning strategy to retired entries as well.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| gallery, | ||
| retiredGallery, |
There was a problem hiding this comment.
Suggestion: These fields are added to the packet, but the later truncation loop only trims recent events/transcript/tool/file sections and never trims gallery fields. If gallery + retired content is large, the function can return a packet still above budget, increasing risk of provider token-limit failures. Include gallery/retired trimming in the truncation phase. [logic error]
Severity Level: Critical 🚨
- ❌ Context packets can exceed configured token budget.
- ⚠️ Provider may fail on token-limit violations.
- ⚠️ Truncation logic misreports effective packet size.Steps of Reproduction ✅
1. `packContext()` builds a full `ShadowContextPacket` including THE GALLERY section from
`packGallery()`: `const { gallery, retiredGallery } = packGallery(options.gallery ?? [],
options.retiredGallery ?? [], tokenBudget)` followed by `gallery, retiredGallery` in the
packet object (src/inference/context-packager.ts:216-234).
2. It then computes `approximateTokens = estimateTokens(packet)` and checks against
`tokenBudget` (context-packager.ts:236-239). If the packet exceeds the budget, it
constructs `truncatedPacket` by copying `recentEvents`, `toolHistory`, `recentTranscript`,
and `fileAttention` into new arrays but leaves `gallery` and `retiredGallery` untouched
(context-packager.ts:241-247).
3. The `trimOldest()` helper used in the truncation loop only removes entries from
`truncatedPacket.recentEvents`, `recentTranscript`, `toolHistory`, and `fileAttention`
(context-packager.ts:249-265). It never trims `truncatedPacket.gallery` or
`truncatedPacket.retiredGallery`. The `while` loop exits once those four arrays are down
to length ≤1, even if `approximateTokens` is still above `tokenBudget`
(context-packager.ts:269-271).
4. In realistic curator scenarios where THE GALLERY carries many artifacts and retired
summaries (fed from `galleryStore.getActive()` and `getRetiredSummaries()` in
shadow-inference-engine.ts:92-95 or replay-runner.ts:503-507), gallery + retired content
can dominate token usage. Even after trimming other sections to minimal size, the packet
may remain over budget because the gallery fields are untouched. Since
`buildContextPacket()` returns only the `packet` from `packContext()` and discards the
`truncated` flag and `approximateTokens` (context-packager.ts:151-157), the inference
engine sends these oversized packets on to providers via `buildInferenceRequest()`
(src/inference/shadow-inference-engine.ts:92-100,
src/inference/prompt-builder.ts:156-163), increasing the chance of hitting provider token
limits mid-session.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/context-packager.ts
**Line:** 232:233
**Comment:**
*Logic Error: These fields are added to the packet, but the later truncation loop only trims recent events/transcript/tool/file sections and never trims gallery fields. If gallery + retired content is large, the function can return a packet still above budget, increasing risk of provider token-limit failures. Include gallery/retired trimming in the truncation phase.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
|
||
| 5. "momentum" — where this is going. | ||
| payload: { "value": 0-100, "label": "short gauge caption", | ||
| "stats": [{ "label", "value", "tone": "good"|"warn"|"bad"|"neutral" }], |
There was a problem hiding this comment.
Suggestion: The prompt asks for momentum tone values that do not match the renderer/type contract (positive|neutral|caution|negative). This causes contract drift where model output can pass parser checks but render with incorrect or missing tone styling. Align prompt enums with the actual exhibit type contract. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Momentum stats render with inconsistent or default styling.
- ⚠️ Tone indicators misrepresent session momentum semantics.
- ⚠️ Prompt/schema drift complicates future validation tooling.Steps of Reproduction ✅
1. The curator system prompt used for all inference calls is defined as
`SHADOW_SYSTEM_PROMPT` (src/inference/prompts.ts:145-295) and is passed to providers as
`systemPrompt` by `buildInferenceRequest()` (src/inference/prompt-builder.ts:156-163).
2. In the momentum exhibit description within the prompt, the stats contract specifies
`tone` values `"good"|"warn"|"bad"|"neutral"` (prompts.ts:241-246), instructing the model
to emit those strings in momentum artifacts.
3. The renderer’s canonical type for momentum stats is `MomentumStat`, whose `tone` field
is declared as `'positive' | 'neutral' | 'caution' | 'negative'`
(src/renderer/exhibits/types.ts:109-113). `MomentumPayload.stats` is an array of these
`MomentumStat` entries (types.ts:128-135), making this vocabulary part of the typed
exhibit contract consumed by ExhibitStage and related components.
4. When the inference engine runs, it parses curator output via `parseCuratorResponse()`,
which validates momentum payloads only structurally (arrays present) via
`payloadIsValid('momentum', payload)` and then returns `ExhibitArtifact` objects
(src/inference/response-parser.ts:221-227,252-52). It does not remap or validate `tone`
values. These artifacts flow into the gallery store and from there to the renderer:
`createInferenceEngine()` calls `galleryStore.applyOps()` and, on gallery updates, invokes
`opts.onGallery(galleryStore.getArtifacts())` (shadow-inference-engine.ts:103-119);
`start-main-process.ts` forwards them to the session manager via
`currentSessionManager.setGallery(artifacts)` (lines 171-177), which injects them in
`SnapshotPayload.gallery` (session-manager.ts:81-113). ExhibitStage receives
`snapshot.gallery` (src/renderer/App.tsx:504-510). Because the prompt-advertised tones
(`"good"|"warn"|"bad"`) do not match the contract (`'positive'|'caution'|'negative'`), any
rendering or styling logic keyed to the contract vocabulary will see unsupported strings
and render incorrect or default tone styling for momentum stats.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/prompts.ts
**Line:** 243:243
**Comment:**
*Api Mismatch: The prompt asks for momentum `tone` values that do not match the renderer/type contract (`positive|neutral|caution|negative`). This causes contract drift where model output can pass parser checks but render with incorrect or missing tone styling. Align prompt enums with the actual exhibit type contract.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
|
||
| 6. "seismograph" — the session as a seismic trace. | ||
| payload: { "trace": [{ "at": "<time>", "magnitude": 0.0-1.0, | ||
| "kind": "commit"|"failure"|"abort"|"milestone"|"churn"|"quiet", |
There was a problem hiding this comment.
Suggestion: The seismograph kind vocabulary in the prompt does not match the typed payload contract, so the model is instructed to emit kinds outside the renderer’s canonical set. This creates inconsistent behavior and weakens semantic rendering fidelity. Use the exact SeismographTremor['kind'] values in the prompt. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Seismograph tremors use inconsistent, unsupported kind categories.
- ⚠️ Downstream analysis misclassifies tremors by noncanonical kinds.
- ⚠️ Prompt/schema drift reduces reliability of seismograph artifacts.Steps of Reproduction ✅
1. The curator prompt’s seismograph exhibit section defines tremor payloads with `kind`
values `"commit"|"failure"|"abort"|"milestone"|"churn"|"quiet"`
(src/inference/prompts.ts:251-256), teaching the model to emit exactly that vocabulary.
2. The canonical type for seismograph tremors is `SeismographTremor` in the renderer’s
contract, where `kind` is declared as `NodeKind | 'merge' | 'abort' | 'failure' | 'quiet'`
(src/renderer/exhibits/types.ts:137-142). `NodeKind` itself is `'goal' | 'turn' | 'tool' |
'file' | 'incident' | 'artifact'` (types.ts:12-16). The strings `"commit"`, `"milestone"`,
and `"churn"` are not part of this union, while `'merge'` is allowed by the type but never
mentioned in the prompt.
3. `validateExhibitArtifact()` in `response-parser.ts` validates seismograph payloads
structurally (trace array, annotations array, numeric `windowMinutes`) via
`payloadIsValid('seismograph', payload)` (response-parser.ts:221-233) but does not enforce
the `kind` enum; any string is accepted as long as the container shape is correct. Thus,
curator outputs containing unsupported `kind` values from the prompt are converted into
`ExhibitArtifact` instances without normalization (response-parser.ts:252-52).
4. These artifacts are then stored and surfaced like all other exhibits:
`createGalleryStore.applyOps()` inserts them into the gallery
(src/inference/gallery-store.ts:171-205), `createInferenceEngine` exposes the gallery to
the renderer via `onGallery(galleryStore.getArtifacts())`
(shadow-inference-engine.ts:105-119), `start-main-process` forwards them into
`SnapshotPayload.gallery` through `SessionManager.setGallery()`
(start-main-process.ts:171-177, session-manager.ts:81-113), and ExhibitStage renders the
gallery (src/renderer/App.tsx:504-510). Because the seismograph `kind` values in the
prompt diverge from the renderer’s canonical vocabulary, tremors may be styled or
interpreted incorrectly, weakening semantic fidelity and making downstream analysis logic
harder to reason about.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/inference/prompts.ts
**Line:** 253:253
**Comment:**
*Api Mismatch: The seismograph `kind` vocabulary in the prompt does not match the typed payload contract, so the model is instructed to emit kinds outside the renderer’s canonical set. This creates inconsistent behavior and weakens semantic rendering fidelity. Use the exact `SeismographTremor['kind']` values in the prompt.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Code Review SummaryStatus: Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
SUGGESTION
Files Reviewed (14 files)
Reviewed by laguna-m.1:free · Input: 220.4K · Output: 7K · Cached: 1.3M |
User description
PR-C: the curator pipeline
Builds on the exhibit component library (PR-B) to make the shadow LLM the
curator of the exhibit floor. Implements the accepted design in
docs/plans/plan-exhibit-floor.mdusing the verbatim prompt indocs/plans/curator-prompt-v2.md.What lands
SHADOW_SYSTEM_PROMPTis the exhibit curator:{ pulse, galleryOps }over the seven-type vocabulary, THE GALLERY fed back as memory,curation over completeness. Installed verbatim; doc comment rewritten with a
2026-07-23 iteration-log milestone.
parseCuratorResponsehandles the curator (v2) shape,the legacy flat (v1) shape, and unparseable output.
validateExhibitArtifactchecks each artifact against the discriminated union in
src/renderer/exhibits/types.ts(envelope + exhibitType membership + per-typepayload required fields); invalid artifacts are dropped with a logged reason.
The legacy
ShadowInsightkinds are derived FROM the new shape, so the statusstrip, vignette, ghost trail, and ShadowPanel keep working unchanged.
ExhibitArtifactmap: create/refresh/retire, fresh→active promotion on subsequent packets, and dual-gated staleness
by
decayClass(fast/medium/slow → stale after N events OR M minutes).payload summary) plus retired-this-session ids+reasons, capped at ~15% of the
packet token budget (envelopes-only, then relevance-pruned, when over).
onGallery; thesession manager rides it on
SnapshotPayload.gallerythrough the existingdirty-refresh path;
Apppasses it toExhibitStage. The fixture gallerystays the boot/fixture-only fallback; live/replay show the curator's floor.
per-firing galleryOps summary (counts + ids), includes the final gallery in
the report, and gains a
--gallery-outflag.paths unchanged; ceiling stays 50) for fewer, bigger curator calls.
Tests
New suites cover the parser (new/legacy shapes, invalid-artifact dropping,
legacy-insight derivation), gallery store (ops, staleness, retire/archive),
packager gallery section (present, capped, retired lines), engine integration
(ops applied → gallery exposed → next packet contains THE GALLERY), and the new
trigger thresholds. Full
npm test(tsc --noEmit && vitest run) is green: 531tests across 60 files, plus the 3 live-session tests in the pre-push hook.
🤖 Generated with Claude Code
https://claude.ai/code/session_01U9EpjZNoWaXLzfG93tdvTU
CodeAnt-AI Description
Add a curator gallery to inference, replay, and the live exhibit floor
What Changed
Impact
✅ Live exhibit floor now shows curated gallery updates✅ Fewer noisy inference calls during routine activity✅ Replay reports can include the final gallery💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.