feat(overseer): operator turn supersedes session inbox (#108) - #109
feat(overseer): operator turn supersedes session inbox (#108)#109heavygee wants to merge 1 commit into
Conversation
Cross-session altitude: an operator turn on S dismisses tip-of-surface inbox cards for S (not resume-alone). query_open_loops only returns whole sessions still needing attention that the operator has not turned on since. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2beca81e3a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (shouldRecordSessionActivity(content)) { | ||
| onSessionActivity?.(sid, msg.createdAt) | ||
| // Local CLI operator turn — tip-of-surface inbox dies (#108). | ||
| store.inbox.supersedeForOperatorTurn(sid) |
There was a problem hiding this comment.
Restrict supersession to human-authored messages
shouldRecordSessionActivity also returns true for an agent ready event, and every launcher emits that event when a session starts or reopens. Consequently, resume/reopen alone enters this branch and obsoletes the session's active inbox items; sessionHasOperatorTurnSince reuses the same predicate and also removes the session from query_open_loops. Use a human-user-message predicate rather than the broader activity predicate.
Useful? React with 👍 / 👎.
| this.onSessionActivity?.(sessionId, msg.createdAt) | ||
| // Operator turn (web / telegram / ping_session) — tip-of-surface inbox dies (#108). | ||
| this.store.inbox.supersedeForOperatorTurn(sessionId) |
There was a problem hiding this comment.
Defer supersession until a scheduled message is delivered
When payload.scheduledAt is in the future, the message is stored now but, as the following branch documents, is not delivered to the CLI until it matures. This unconditional call therefore clears the session's attention cards up to seven days before the operator actually turns on the session; canceling the scheduled message does not restore those cards. Skip future-scheduled rows here and supersede them from the maturity path instead.
Useful? React with 👍 / 👎.
| const recent = this.messages.getMessages(sessionId, 80) | ||
| return recent.some( | ||
| (message) => message.createdAt > sinceTs && shouldRecordSessionActivity(message.content) |
There was a problem hiding this comment.
Search the complete message history for operator turns
This checks only the latest 80 transcript rows. If an operator continues a session and that turn is followed by more than 80 agent/output chunks without a newer status-bearing event, the turn falls outside this window and the old need is incorrectly returned by query_open_loops as forgotten. Use a database existence query over all messages since sinceTs, or persist the last operator-turn timestamp per session.
Useful? React with 👍 / 👎.
| let count = 0 | ||
| for (const item of items) { | ||
| const updated = recordInboxOperatorAction(db, item.id, 'dismiss', feedback, null) | ||
| if (updated) count += 1 |
There was a problem hiding this comment.
Keep implicit supersession out of disposition labels
Each ordinary session turn routes the automatic transition through recordInboxOperatorAction, which inserts action='dismiss' into inbox_operator_actions. That table is explicitly consumed by query_dispositions and clustering as operator decision/training history, so routine continuations will be reported and learned as explicit dismissal preferences. Update the inbox item's status without creating a disposition row, or exclude superseded_by_operator_turn rows from disposition queries and discovery.
Useful? React with 👍 / 👎.
| this.onSessionActivity?.(sessionId, msg.createdAt) | ||
| // Operator turn (web / telegram / ping_session) — tip-of-surface inbox dies (#108). | ||
| this.store.inbox.supersedeForOperatorTurn(sessionId) |
There was a problem hiding this comment.
Skip supersession for idempotent message replays
addMessage returns the previously stored row when a web request repeats an existing localId, but this side effect still runs as if a new operator turn landed. If that retry is delayed until after the worker has emitted a newer attention event, replaying the old request incorrectly obsoletes the new card even though the operator sent nothing new. Only supersede when the message insert actually created a row.
Useful? React with 👍 / 👎.
Summary
ping_session) dismisses active inbox items for that session (superseded_by_operator_turn).query_open_loops/ "what am I forgetting?" = whole sessions still needing attention that the operator has not turned on since that need — not within-session memory.Test plan
hub/src/store/inboxItems.test.tssupersede + re-promotequery_open_loopsexcludes operator-touched sessionsMade with Cursor