Skip to content

fix(insights): add queued status and fix action lifecycle semantics#52

Open
serrrfirat wants to merge 7 commits into
mainfrom
dmux-1771586022532
Open

fix(insights): add queued status and fix action lifecycle semantics#52
serrrfirat wants to merge 7 commits into
mainfrom
dmux-1771586022532

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Feb 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds 'queued' intermediate status to the insight lifecycle (newviewedqueuedacted / back to viewed), preventing insights from being permanently mislabeled as 'acted' when their action job fails
  • Splits selectInsightAction into queueInsightAction (job creation) and markInsightActed (bridge feedback for already-completed actions)
  • Cascades execution results to insight status: updateExecutionStatus now marks the parent insight 'acted' on completion, or reverts to 'viewed' on failure (guarded to not overwrite a successful execution)
  • Fixes API error semantics: POST /api/insights/:id/act now returns 202 Accepted on success, 404 for missing insights, and 422 for other errors — instead of 200 with { success: false }

Test plan

  • bunx tsc --noEmit — zero type errors
  • bun test — all 55 tests pass
  • Manual: create an action job via API, verify insight shows 'queued' not 'acted'
  • Manual: verify API returns 202 on success, 404 on missing insight, 422 on other errors
  • Manual: verify failed execution reverts insight to 'viewed'

Closes #38

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Insights now include a "queued" status to reflect queued actions.
    • Action requests return clearer HTTP responses (202 when queued; 404/422 on errors).
  • Improvements

    • Insight status tracking now distinguishes viewed vs. acted transitions for more accurate lifecycle reporting.
    • Bias preference parsing and validation improved (balanced, novelty, consistency).
    • Ranking telemetry consolidated for simpler, more efficient reporting.
  • Tests

    • Expanded ranking and telemetry tests to cover new behaviors.

serrrfirat and others added 4 commits February 19, 2026 21:49
… tests

- Fix similarity() bias: use Math.max denominator to prevent inflated scores
- Fix mutation of shared ranked objects by cloning before modifying keep/suppressionReasons
- Use stable rank-based identity keys instead of fragile rank:topic strings
- Extract parseInsightBiasMode to config.ts (was duplicated in api-server + index)
- Extract recordRankingTelemetry helper to insight-ranker.ts (was duplicated 50+ lines in cli + daemon)
- Filter telemetry events at SQL level in getInsightQualityMetrics instead of loading all events
- Add 3 new tests: consistency bias, exploration guarantee swap, feedback accumulation
- Add JSDoc for scoring formula and document normalizeActions .slice(0,4) cap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…etry tests

- Add skipSelectedForSurface option to recordRankingTelemetry so the daemon
  does not double-emit insight_selected_for_surface (it records its own
  after saveInsight with the real entityId)
- Add 2 tests for recordRankingTelemetry: correct event counts for mixed
  kept/suppressed results, and skipSelectedForSurface behavior
- Add inline comments noting shallow clone safety for nested read-only objects

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ass event classification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Insight status was incorrectly set to 'acted' at job queue time. If the
job later failed, the insight was permanently mislabeled. This introduces
a 'queued' intermediate status and cascades the final status from
execution results. Also fixes the API to return proper HTTP status codes
(202 Accepted, 404, 422) instead of 200 with { success: false }.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @serrrfirat, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the robustness and clarity of the insight action lifecycle. By introducing a 'queued' status and refining the distinction between queuing an action and marking it as acted, the system now accurately reflects the state of an insight's associated job. Furthermore, the changes ensure that insight statuses are automatically updated based on the success or failure of their corresponding executions, and API responses for action requests are more precise, leading to a more reliable and understandable user experience.

Highlights

  • New 'queued' insight status: Introduced a 'queued' intermediate status in the insight lifecycle (new → viewed → queued → acted / back to viewed) to prevent insights from being incorrectly marked as 'acted' when their action job fails.
  • Action lifecycle semantics refined: The selectInsightAction function was split into queueInsightAction (for job creation) and markInsightActed (for feedback on already-completed actions), clarifying the intent at different stages of the action process.
  • Execution results cascade to insight status: The updateExecutionStatus function now automatically updates the parent insight's status: marking it 'acted' upon successful completion, or reverting it to 'viewed' if the execution fails (with a guard to prevent overwriting a successful 'acted' status).
  • API error semantics improved: The POST /api/insights/:id/act endpoint now returns 202 Accepted on successful job queuing, 404 Not Found for missing insights, and 422 Unprocessable Entity for other validation errors, providing clearer API responses.
  • Telemetry for insight ranking refactored: Telemetry recording for insight ranking has been centralized into a new recordRankingTelemetry function, reducing duplication and improving consistency across CLI and daemon processes.
Changelog
  • src/action-executor.ts
    • Updated imports to use queueInsightAction and updateInsightStatus instead of selectInsightAction
    • Replaced calls to selectInsightAction with queueInsightAction when an action is initiated
    • Added calls to updateInsightStatus to mark insights as 'viewed' on execution failure
    • Added calls to updateInsightStatus to mark insights as 'acted' on execution completion
  • src/action-service.ts
    • Updated imports to use queueInsightAction instead of selectInsightAction
    • Replaced calls to selectInsightAction with queueInsightAction when creating an action job
  • src/api-server.ts
    • Imported parseInsightBiasMode from config.js
    • Added 'queued' to the VALID_INSIGHT_STATUSES set
    • Removed the local parseInsightBiasMode function, now imported from config.js
    • Updated the type definition for status in the getInsights endpoint to include 'queued'
    • Modified the POST /api/insights/:id/act endpoint to return 202 Accepted on success and specific error codes (404, 422) for failures
  • src/bridge/feedback-handler.ts
    • Updated imports to use markInsightActed instead of selectInsightAction
    • Replaced calls to selectInsightAction with markInsightActed for recording completed actions
  • src/config.ts
    • Moved the parseInsightBiasMode function from api-server.ts to this file for better organization
  • src/index.ts
    • Removed selectInsightAction from synapse-db imports
    • Added recordRankingTelemetry to insight-ranker imports
    • Replaced manual telemetry event recording for insight ranking with a call to recordRankingTelemetry
    • Updated the import for parseInsightBiasMode to reflect its new location in config.js
    • Added display logic for the new 'queued' insight status in the CLI output
    • Updated insight count summary to include 'queued' insights
  • src/insight-ranker.test.ts
    • Imported recordRankingTelemetry for testing
    • Added new test cases for consistency bias mode's effect on novel topics
    • Added a test case for the exploration guarantee mechanism
    • Added a test case for accumulated feedback signals and clamping
    • Added a new test suite for recordRankingTelemetry to verify event emission
  • src/insight-ranker.ts
    • Imported TelemetrySource and recordTelemetryEvent
    • Added JSDoc comments to the rankInsights function for clarity
    • Improved the fallback selection logic by creating a shallow clone of the ranked entry
    • Updated the selectedKeys logic to use item.rank for better uniqueness and consistency
    • Added a comment explaining the capping of actions per insight
    • Introduced recordRankingTelemetry function to centralize telemetry event recording for insight ranking
  • src/scripts/daemon.ts
    • Imported recordRankingTelemetry for telemetry handling
    • Replaced manual telemetry event recording for insight ranking with a call to recordRankingTelemetry
  • src/synapse-db.ts
    • Split the selectInsightAction function into queueInsightAction (for queuing an action) and markInsightActed (for marking an action as completed)
    • Updated getInsightCounts to include the new 'queued' status in its aggregation
    • Refactored getInsightQualityMetrics to optimize telemetry event retrieval and parsing with a single-pass classification
    • Added cascade logic to updateExecutionStatus to mark the parent insight 'acted' on completion
    • Added cascade logic to updateExecutionStatus to revert the parent insight to 'viewed' on failure, only if its status is 'queued'
  • src/types.ts
    • Added 'queued' as a valid status type for Insight objects
Activity
  • The author, serrrfirat, provided a detailed summary of the changes, including the introduction of a 'queued' status and refined action lifecycle semantics.
  • A comprehensive test plan was outlined, covering type checks, unit tests, and manual verification for API responses and insight status transitions.
  • The pull request was generated with the assistance of 'Claude Code', indicating the use of an AI tool in its development.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request successfully introduces the 'queued' status to the insight lifecycle and improves action semantics by cascading execution results to the parent insight. It also refines API response codes to follow RESTful conventions. However, there are logic discrepancies in the similarity calculation that may degrade personalization accuracy, and redundant status updates in the action executor that bypass the newly implemented guards in the database layer.

Comment thread src/action-executor.ts Outdated
// No content at all
const errorMsg = 'No output generated';
updateExecutionStatus(db, executionId, 'failed', errorMsg, 'unknown');
updateInsightStatus(db, insight.id!, 'viewed');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This call to updateInsightStatus is now redundant because updateExecutionStatus (called on the previous line) already handles the cascade to the parent insight. Furthermore, calling it explicitly here bypasses the guard logic implemented in synapse-db.ts (which only reverts to 'viewed' if the status is still 'queued'), potentially leading to inconsistent states or duplicate telemetry events.

Comment thread src/insight-ranker.ts Outdated
const setB = new Set(b);
const overlap = a.filter(token => setB.has(token)).length;
const denominator = Math.max(1, Math.min(a.length, b.length));
const denominator = Math.max(1, Math.max(a.length, b.length));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Changing the denominator from Math.min to Math.max significantly alters the similarity heuristic. Using Math.max makes the score much more conservative, especially when comparing sets of different lengths (e.g., a short topic vs. a long description). This inconsistency with the deduplication logic in synapse-db.ts (which still uses Math.min) may cause the ranker to fail to identify related topics, leading to poor personalization or duplicate insights being surfaced.

Suggested change
const denominator = Math.max(1, Math.max(a.length, b.length));
const denominator = Math.max(1, Math.min(a.length, b.length));

Comment thread src/action-executor.ts Outdated
}

updateExecutionStatus(db, executionId, 'completed', undefined, 'unknown');
updateInsightStatus(db, insight.id!, 'acted');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the failure case, this explicit update to 'acted' is redundant as it is now handled automatically by the updateExecutionStatus(..., 'completed', ...) call on line 629.

Comment thread src/synapse-db.ts Outdated
});

// Cascade: mark the parent insight as 'acted'
const execRow = db.prepare(`SELECT insight_id FROM action_executions WHERE id = ?`).get(executionId) as { insight_id: number } | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The query to fetch insight_id is repeated at line 3049. Consider refactoring this into a single variable at the beginning of the function when the status is terminal (completed, failed, or cancelled) to avoid redundant database lookups.

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: REQUEST CHANGES

Blocking Issues

  1. HIGH: Double-write on insight status — Every updateExecutionStatus(db, executionId, 'completed') cascades an internal updateInsightStatus call, then the caller ALSO calls updateInsightStatus directly. Result: 2x UPDATE queries and 2x telemetry events per execution. Fix: remove explicit updateInsightStatus calls from action-executor.ts (keep cascade in synapse-db.ts).

  2. HIGH: Success cascade lacks guardupdateExecutionStatus('completed') unconditionally sets insight to 'acted', but the failure path IS guarded by if (insightRow?.status === 'queued'). If a user dismisses an insight while a job runs, the success cascade overwrites 'dismissed' with 'acted'.

Non-Blocking Issues

  • similarity() denominator change (same as PR #50/#51)
  • No transaction wrapping around status + telemetry multi-statement operations
  • createJob uses a different DB connection than the caller's db
  • API error matching uses fragile string comparison (result.error === 'Action or insight not found')
  • Non-null assertion insight.id! used without guard

…Jaccard

- Remove redundant updateInsightStatus calls from action-executor.ts
  since updateExecutionStatus already cascades insight status changes
- Guard the success cascade in updateExecutionStatus so 'completed'
  does not overwrite 'dismissed' with 'acted'
- Extract insight_id lookup to a single query at the top of the
  terminal-status block, eliminating duplicate DB queries
- Fix similarity() denominator to use true Jaccard (|A|+|B|-|A∩B|)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@serrrfirat has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 13 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces an insight action "queued" lifecycle: jobs now queue actions (queueInsightAction) and only mark insights acted on successful execution (markInsightActed/status updates). Consolidates bias parsing into parseInsightBiasMode, adds ranking telemetry (recordRankingTelemetry), and returns proper HTTP statuses for action-creation failures.

Changes

Cohort / File(s) Summary
Types & DB
src/types.ts, src/synapse-db.ts
Adds 'queued' to Insight.status; replaces selectInsightAction with queueInsightAction, adds markInsightActed, updates counts to include queued, and adjusts status-transition logic for queued→viewed/acted.
Action execution & service
src/action-executor.ts, src/action-service.ts
Switches to queue-first flow (queueInsightAction), adds status transitions (mark viewed on failures, acted when execution completes), and aligns imports/usage.
API & bias parsing
src/api-server.ts, src/config.ts
Moves bias parsing to exported parseInsightBiasMode; validates allowed bias modes; accepts 'queued' in status unions; returns 202/404/422 on action creation results.
Feedback bridge
src/bridge/feedback-handler.ts
Replaces selectInsightAction usage with markInsightActed for recorded/implicit feedback paths.
Ranking & telemetry
src/insight-ranker.ts, src/insight-ranker.test.ts
Adds recordRankingTelemetry export and logic to emit per-candidate telemetry; refactors rank-based selection/suppression and caps actions list; expands tests to cover telemetry and bias behaviors.
Telemetry integration points
src/index.ts, src/scripts/daemon.ts
Replaces per-item telemetry loops with single recordRankingTelemetry calls; imports updated to use new telemetry and bias utilities.
Misc
.gitignore, other small files
Adds .dmux/ to .gitignore; minor import/usage adjustments across modules to accommodate renamed functions.

Sequence Diagram

sequenceDiagram
    actor Client
    participant API as API Server
    participant AS as Action Service
    participant AE as Action Executor
    participant DB as Database
    participant Telemetry

    Client->>API: POST /api/insights/:id/act
    API->>AS: createActionJob(insightId, action)
    AS->>DB: queueInsightAction(db, insightId, actionLabel, source?)
    DB->>DB: SET insight.status = 'queued', record selected_action
    DB->>Telemetry: insight_action_queued
    AS-->>API: { success: true }
    API-->>Client: 202 Accepted (queued)

    rect rgba(100, 200, 100, 0.5)
    Note over AE: Async Execution — Success path
    AE->>AE: Execute action
    AE-->>AE: Success
    AE->>DB: markInsightActed(db, insightId, actionLabel, source?)
    DB->>DB: SET insight.status = 'acted'
    DB->>Telemetry: insight_acted
    end

    rect rgba(200, 100, 100, 0.5)
    Note over AE: Async Execution — Failure path
    AE->>AE: Execute action
    AE-->>AE: Failure
    AE->>DB: updateInsightStatus(db, insightId, 'viewed') if status == 'queued'
    DB->>DB: SET insight.status = 'viewed'
    DB->>Telemetry: insight_status_reverted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐇 I queued the action, soft and neat,
Then hopped away to tap my feet.
When runners finish, I stamp "acted" true,
If something broke, I mark "viewed" anew.
Telemetry twinkles — carrots for clues.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a 'queued' status and fixing action lifecycle semantics, which directly matches the core changes across multiple files.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #38: introduces 'queued' status to separate job creation from execution completion, returns proper HTTP codes (202/404/422) instead of 200 with false, and enables status-based error handling by clients.
Out of Scope Changes check ✅ Passed The only out-of-scope change is adding .dmux/ to .gitignore, which appears to be a minor unrelated cleanup and does not impact the core objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dmux-1771586022532

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synapse-db.ts (1)

3037-3057: ⚠️ Potential issue | 🟡 Minor

Add guard against reverting insight while other executions are in-flight.

If multiple executions per insight are possible, a failed/cancelled execution could revert the insight to viewed while another execution is still pending/running. Add a check for other in-flight executions before downgrading the insight status:

Suggested guard
     if (execRow?.insight_id) {
       const insightRow = db.prepare(`SELECT status FROM insights WHERE id = ?`).get(execRow.insight_id) as { status: string } | null;
       if (insightRow?.status === 'queued') {
+        const otherInFlight = db.prepare(
+          `SELECT 1 FROM action_executions WHERE insight_id = ? AND status IN ('pending', 'running') AND id != ? LIMIT 1`
+        ).get(execRow.insight_id, executionId);
+        if (!otherInFlight) {
           updateInsightStatus(db, execRow.insight_id, 'viewed', source);
+        }
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synapse-db.ts` around lines 3037 - 3057, Before downgrading the related
insight in the failed/cancelled branch, query the action_executions table for
other in-flight executions for the same execRow.insight_id (excluding the
current executionId) and only call updateInsightStatus(db, execRow.insight_id,
'viewed', source) if that count is zero; specifically, use a prepared statement
(e.g., SELECT COUNT(1) AS cnt FROM action_executions WHERE insight_id = ? AND id
!= ? AND status IN ('queued','pending','running')) and check cnt === 0 before
reverting the insight status.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.ts`:
- Around line 1645-1647: The total currently sums counts.new, counts.viewed,
counts.queued, counts.acted, and counts.dismissed but the console.log summary
only lists new/viewed/queued/acted, causing a mismatch; update the logging
statement in the same block (where total is calculated and the console.log is
emitted) to include counts.dismissed in the parentheses (or alternatively remove
counts.dismissed from the total calculation) so the printed breakdown matches
the computed total—look for the total variable and the console.log call that
formats the summary and add the dismissed count label (counts.dismissed || 0) to
the message.

In `@src/insight-ranker.ts`:
- Around line 515-569: The telemetry currently uses ranked.keep (from
ranking.ranked) which can be false for items later promoted by exploration;
change recordRankingTelemetry to determine "kept" from the final selection set
(ranking.selected) instead: compute a boolean like const isKept =
ranking.selected.some(s => s.id === ranked.id) (or compare the same unique
identifier used elsewhere) and use isKept in the metadata (replace kept:
ranked.keep) and in the conditionals that emit insight_selected_for_surface vs
insight_suppressed (respecting options.skipSelectedForSurface). Update all
places that reference ranked.keep for telemetry to use this derived isKept value
so promoted candidates are correctly recorded as kept.

In `@src/synapse-db.ts`:
- Around line 1344-1364: queueInsightAction currently emits an eventType of
'insight_action_queued' which will break existing metrics that expect
'insight_action_selected'; to fix, update queueInsightAction to also emit the
legacy signal by calling recordTelemetryEvent a second time with eventType
'insight_action_selected' (same source, entityType 'insight', entityId
insightId, and metadata { actionLabel }) so both new and existing metrics
continue to work.

---

Outside diff comments:
In `@src/synapse-db.ts`:
- Around line 3037-3057: Before downgrading the related insight in the
failed/cancelled branch, query the action_executions table for other in-flight
executions for the same execRow.insight_id (excluding the current executionId)
and only call updateInsightStatus(db, execRow.insight_id, 'viewed', source) if
that count is zero; specifically, use a prepared statement (e.g., SELECT
COUNT(1) AS cnt FROM action_executions WHERE insight_id = ? AND id != ? AND
status IN ('queued','pending','running')) and check cnt === 0 before reverting
the insight status.

Comment thread src/index.ts
Comment on lines +1645 to +1647
const total = (counts.new || 0) + (counts.viewed || 0) + (counts.queued || 0) + (counts.acted || 0) + (counts.dismissed || 0);
if (total > insights.length) {
console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.acted || 0} acted on)`));
console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.queued || 0} queued, ${counts.acted || 0} acted on)`));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Total includes dismissed insights but summary omits them.

The total count includes dismissed, but the label doesn’t, so the breakdown may not add up. Consider including dismissed in the summary (or exclude it from total).

🛠️ Suggested fix
-      console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.queued || 0} queued, ${counts.acted || 0} acted on)`));
+      console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.queued || 0} queued, ${counts.acted || 0} acted on, ${counts.dismissed || 0} dismissed)`));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const total = (counts.new || 0) + (counts.viewed || 0) + (counts.queued || 0) + (counts.acted || 0) + (counts.dismissed || 0);
if (total > insights.length) {
console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.acted || 0} acted on)`));
console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.queued || 0} queued, ${counts.acted || 0} acted on)`));
const total = (counts.new || 0) + (counts.viewed || 0) + (counts.queued || 0) + (counts.acted || 0) + (counts.dismissed || 0);
if (total > insights.length) {
console.log(chalk.gray(`Total: ${total} insights (${counts.new || 0} new, ${counts.viewed || 0} viewed, ${counts.queued || 0} queued, ${counts.acted || 0} acted on, ${counts.dismissed || 0} dismissed)`));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.ts` around lines 1645 - 1647, The total currently sums counts.new,
counts.viewed, counts.queued, counts.acted, and counts.dismissed but the
console.log summary only lists new/viewed/queued/acted, causing a mismatch;
update the logging statement in the same block (where total is calculated and
the console.log is emitted) to include counts.dismissed in the parentheses (or
alternatively remove counts.dismissed from the total calculation) so the printed
breakdown matches the computed total—look for the total variable and the
console.log call that formats the summary and add the dismissed count label
(counts.dismissed || 0) to the message.

Comment thread src/insight-ranker.ts
Comment on lines +515 to +569
export function recordRankingTelemetry(
db: Parameters<typeof recordTelemetryEvent>[0],
ranking: InsightRankingResult,
options: {
source: TelemetrySource;
biasMode: string;
provider?: string;
skipSelectedForSurface?: boolean;
}
): void {
for (const ranked of ranking.ranked) {
const commonMetadata = {
...(options.provider ? { provider: options.provider } : {}),
biasMode: options.biasMode,
score: ranked.score,
rank: ranked.rank,
isNovel: ranked.isNovel,
topicSimilarity: ranked.topicSimilarity,
};

recordTelemetryEvent(db, {
eventType: 'insight_ranked',
source: options.source,
entityType: 'insight_candidate',
metadata: {
...commonMetadata,
kept: ranked.keep,
memoryAlignment: ranked.features.memoryAlignment,
feedbackAdjustment: ranked.features.feedbackAdjustment,
noveltyPenalty: ranked.features.noveltyPenalty,
actionability: ranked.features.actionability,
matchedFeedbackCount: ranked.matchedFeedbackCount,
matchedMemoryCount: ranked.matchedMemoryCount,
suppressionReasons: ranked.suppressionReasons,
},
});

if (ranked.keep && !options.skipSelectedForSurface) {
recordTelemetryEvent(db, {
eventType: 'insight_selected_for_surface',
source: options.source,
entityType: 'insight_candidate',
metadata: commonMetadata,
});
} else if (!ranked.keep) {
recordTelemetryEvent(db, {
eventType: 'insight_suppressed',
source: options.source,
entityType: 'insight_candidate',
metadata: {
...commonMetadata,
suppressionReasons: ranked.suppressionReasons,
},
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Telemetry “kept” should reflect final selection, not threshold-only.

recordRankingTelemetry uses ranked.keep, but exploration promotion can select candidates whose keep is still false in ranking.ranked. That mislabels promoted candidates as suppressed and skews selection metrics. Consider deriving “kept” from ranking.selected.

🛠️ Suggested fix
 export function recordRankingTelemetry(
   db: Parameters<typeof recordTelemetryEvent>[0],
   ranking: InsightRankingResult,
   options: {
     source: TelemetrySource;
     biasMode: string;
     provider?: string;
     skipSelectedForSurface?: boolean;
   }
 ): void {
+  const selectedRanks = new Set(ranking.selected.map(entry => entry.rank));
   for (const ranked of ranking.ranked) {
+    const kept = selectedRanks.has(ranked.rank);
     const commonMetadata = {
       ...(options.provider ? { provider: options.provider } : {}),
       biasMode: options.biasMode,
       score: ranked.score,
       rank: ranked.rank,
       isNovel: ranked.isNovel,
       topicSimilarity: ranked.topicSimilarity,
     };

     recordTelemetryEvent(db, {
       eventType: 'insight_ranked',
       source: options.source,
       entityType: 'insight_candidate',
       metadata: {
         ...commonMetadata,
-        kept: ranked.keep,
+        kept,
         memoryAlignment: ranked.features.memoryAlignment,
         feedbackAdjustment: ranked.features.feedbackAdjustment,
         noveltyPenalty: ranked.features.noveltyPenalty,
         actionability: ranked.features.actionability,
         matchedFeedbackCount: ranked.matchedFeedbackCount,
         matchedMemoryCount: ranked.matchedMemoryCount,
         suppressionReasons: ranked.suppressionReasons,
       },
     });

-    if (ranked.keep && !options.skipSelectedForSurface) {
+    if (kept && !options.skipSelectedForSurface) {
       recordTelemetryEvent(db, {
         eventType: 'insight_selected_for_surface',
         source: options.source,
         entityType: 'insight_candidate',
         metadata: commonMetadata,
       });
-    } else if (!ranked.keep) {
+    } else if (!kept) {
       recordTelemetryEvent(db, {
         eventType: 'insight_suppressed',
         source: options.source,
         entityType: 'insight_candidate',
         metadata: {
           ...commonMetadata,
           suppressionReasons: ranked.suppressionReasons,
         },
       });
     }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/insight-ranker.ts` around lines 515 - 569, The telemetry currently uses
ranked.keep (from ranking.ranked) which can be false for items later promoted by
exploration; change recordRankingTelemetry to determine "kept" from the final
selection set (ranking.selected) instead: compute a boolean like const isKept =
ranking.selected.some(s => s.id === ranked.id) (or compare the same unique
identifier used elsewhere) and use isKept in the metadata (replace kept:
ranked.keep) and in the conditionals that emit insight_selected_for_surface vs
insight_suppressed (respecting options.skipSelectedForSurface). Update all
places that reference ranked.keep for telemetry to use this derived isKept value
so promoted candidates are correctly recorded as kept.

Comment thread src/synapse-db.ts
Comment on lines 1344 to +1364
/**
* Record which action the user selected for an insight
* Queue an action for an insight — sets status to 'queued' and records selected_action.
* Used when an action job is created but not yet executed.
*/
export function selectInsightAction(
export function queueInsightAction(
db: Database,
insightId: number,
actionLabel: string,
source: TelemetrySource = 'unknown'
): void {
const stmt = db.prepare(`UPDATE insights SET status = 'queued', selected_action = ? WHERE id = ?`);
stmt.run(actionLabel, insightId);

recordTelemetryEvent(db, {
eventType: 'insight_action_queued',
source,
entityType: 'insight',
entityId: insightId,
metadata: { actionLabel },
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Telemetry signal change may break pilot metrics.

queueInsightAction now emits insight_action_queued, but pilot metrics still query insight_action_selected. That will undercount action selection milestones for the normal queue→execute path. Consider emitting the legacy event as well (or update the metrics to use insight_action_queued).

🛠️ Possible compatibility fix
   recordTelemetryEvent(db, {
     eventType: 'insight_action_queued',
     source,
     entityType: 'insight',
     entityId: insightId,
     metadata: { actionLabel },
   });
+
+  // Backward-compatible signal for pilot metrics that track selection.
+  recordTelemetryEvent(db, {
+    eventType: 'insight_action_selected',
+    source,
+    entityType: 'insight',
+    entityId: insightId,
+    metadata: { actionLabel, queued: true },
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Record which action the user selected for an insight
* Queue an action for an insight sets status to 'queued' and records selected_action.
* Used when an action job is created but not yet executed.
*/
export function selectInsightAction(
export function queueInsightAction(
db: Database,
insightId: number,
actionLabel: string,
source: TelemetrySource = 'unknown'
): void {
const stmt = db.prepare(`UPDATE insights SET status = 'queued', selected_action = ? WHERE id = ?`);
stmt.run(actionLabel, insightId);
recordTelemetryEvent(db, {
eventType: 'insight_action_queued',
source,
entityType: 'insight',
entityId: insightId,
metadata: { actionLabel },
});
}
/**
* Queue an action for an insight sets status to 'queued' and records selected_action.
* Used when an action job is created but not yet executed.
*/
export function queueInsightAction(
db: Database,
insightId: number,
actionLabel: string,
source: TelemetrySource = 'unknown'
): void {
const stmt = db.prepare(`UPDATE insights SET status = 'queued', selected_action = ? WHERE id = ?`);
stmt.run(actionLabel, insightId);
recordTelemetryEvent(db, {
eventType: 'insight_action_queued',
source,
entityType: 'insight',
entityId: insightId,
metadata: { actionLabel },
});
// Backward-compatible signal for pilot metrics that track selection.
recordTelemetryEvent(db, {
eventType: 'insight_action_selected',
source,
entityType: 'insight',
entityId: insightId,
metadata: { actionLabel, queued: true },
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synapse-db.ts` around lines 1344 - 1364, queueInsightAction currently
emits an eventType of 'insight_action_queued' which will break existing metrics
that expect 'insight_action_selected'; to fix, update queueInsightAction to also
emit the legacy signal by calling recordTelemetryEvent a second time with
eventType 'insight_action_selected' (same source, entityType 'insight', entityId
insightId, and metadata { actionLabel }) so both new and existing metrics
continue to work.

serrrfirat and others added 2 commits February 21, 2026 13:35
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix insight action lifecycle and /api/insights/:id/act error semantics

1 participant