feat(chat): DM continuity — a reply to a run's DM goes back into that run (v0.297.0) - #543
Merged
Merged
Conversation
… run (v0.297.0) `ask_human` and approvals were already answerable from the DM (question_dms / approval_dms). Every other push was one-way: an agent's `notify` and the session lifecycle DMs pinged a human who then had nowhere to reply — the Inbox card is an `update` with no reply box, and a DM reply fell through to the intent router, spawning a FRESH session that knew nothing about the run that asked. Adds the third binding, `session_dms`, written by notifyMember/notifySessionEvent, and Automations.continueSessionDm on the way back in — the DM-keyed analogue of thread continuity: deliver into the live claude, else revive the same transcript. Checked after both decision paths and before the router. A session has no "no longer pending" state to expire against, so the claim is bounded instead: a 24h window, archived/unresumable rows excluded, visibility re-checked against the current member, and an explicit /other-agent still redirects. On success the run's chat egress is pointed at the DM (INSERT OR IGNORE — never steals a run already answering in a thread) and the socket acks by agent name. Also fixes all three bindings running before deliverDM, which is where a member with no linked Slack handle is auto-linked from their email — so a first-time recipient was DM'd a question whose reply then matched nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #540, which fixed the Slack-side half of "I get these messages and have no way to respond." This fixes the product half.
The gap
ask_humanand approvals were already answerable straight from the DM (question_dms/approval_dms). Every other push was one-way — an agent'snotify, and the session-lifecycle DMs (finished / crashed / waiting). The human got pinged with nowhere to reply:update, which has no reply affordanceReported from the field: an agent asked for a design decision via
notify, said it was holding the PR, and the answer had no way back to it.The fix
A third binding,
session_dms, written bynotifyMember/notifySessionEvent, andAutomations.continueSessionDmon the way back in — the DM-keyed analogue of thread continuity (continueSlackThread), which keys on channel+thread that a DM doesn't have:--resumeing the transcript, seeded with the messageOrdered after both decision paths (a pending approval/question is the more specific claim on the same reply) and before the router. Both platforms.
Guards
Its two siblings self-expire — the bound row leaves
pendingand stops matching. A session has no such terminal state, so the claim is bounded another way:SESSION_DM_WINDOW_MS) — a run that pinged you last month can't swallow today's unrelated "hey"/other-agent …still redirects to the routerOn success the run's chat egress is pointed at the DM (
bindReplyChannel,INSERT OR IGNORE— it never steals a run already answering in a thread), so its reply comes back where the human is talking; the socket acks by agent name, since a console-spawned run has no chat egress until it relaunches and silence would read as the reply being swallowed all over again.Also fixed
All three DM bindings were written before
deliverDM— butdeliverDMis where a member with no linked Slack handle is discovered from their email and persisted to the identity map. A first-time recipient was therefore DM'd a question or approval whose reply matched nothing. The bind loop now runs after delivery, through one sharedbindDmRecipients.Verified
New
scripts/dm-continuity-test.cjs— 29 assertions over the binding, every guard above, the three continuation outcomes, the reply-channel adopt/never-steal rule, and the audit line; wired intonpm run test:governance.Plus
npm run typecheck,npm run build,cd web && npm run build. Schema isCREATE TABLE IF NOT EXISTSin the boot pass — no migration step.🤖 Generated with Claude Code