Skip to content

fix: route Slack dispatch replies to babysitters - #287

Open
khaliqgant wants to merge 5 commits into
mainfrom
codex/factory-273-slack-babysit-0817
Open

fix: route Slack dispatch replies to babysitters#287
khaliqgant wants to merge 5 commits into
mainfrom
codex/factory-273-slack-babysit-0817

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Aug 17, 2026

Copy link
Copy Markdown
Member

Fixes #273
Fixes #227

Summary

  • reserve a durable conversation for every Factory dispatch thread, even before an agent has a resumable session, then bind it to the PR babysitter when ownership transfers
  • deliver the complete Slack reply through durable fleet.resume context instead of Relay DM injection, and post a visibly acknowledged receipt only after durable enqueue succeeds
  • persist compact triage and terminal-grace watcher metadata so startup can replay replies received while Factory was stopped
  • retain completed dispatch watchers for a bounded 24-hour grace period so replies after agents exit receive an explicit, retryable no-active-agent response
  • fence terminal cleanup against in-flight conversation routing, visibly surface queued replies before clearing them, and use a durable retirement watermark so restart replay never relabels historical replies
  • migrate legacy conversation receipts and expired unowned deliveries without duplicate notices or stale durable claims
  • integrate the Slack state shape with the host-neutral document-store port from Expose a host-neutral document state-store port #285
  • retain the existing bot guard, exact channel/thread filtering, and post-success-only in-memory dedupe

Discriminating coverage

  • human dispatch reply routes to the babysitter, not the reviewer
  • long reply remains intact across the missing-owner handoff
  • dispatch and triage threads rearm after daemon restart
  • a terminal-thread reply received while Factory is stopped gets a visible fallback after restart
  • an acknowledged reply pending in the coalescing window is surfaced before terminal cleanup
  • a historical reply routed before terminal retirement is not replayed as unroutable
  • legacy terminal and conversation state is migrated before replay
  • expired unowned delivery claims are durably requeued across another restart
  • bot and unrelated-thread replies do not route
  • a failed visible receipt remains replayable and retries

The new triage-restart, receipt-retry, and missing-owner long-reply tests were applied test-only to eaaa076; all failed there as expected before the implementation.

Verification

  • npm run build
  • npm run featuremap:check
  • full orchestrator suite — 491/491
  • four state/document suites — 46/46
  • focused post-rebase Slack/restart/clarification cases — pass
  • final GitHub checks: package, Kubernetes provider E2E, load E2E, verification-gate E2E, verification-stack E2E — pass
  • all automated review findings addressed; threads resolved (one low-confidence rate-limit suggestion intentionally declined because it conflicts with the per-reply observability acceptance requirement)
  • git diff --check
  • added-lines credential-pattern scan: no matches

The repository-requested Veto MCP diff review was unavailable in this worker lane, so the checks above and a manual diff review are the fallback review record.

@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.


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.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21b31ab3d3

ℹ️ 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".

Comment thread src/orchestrator/factory.ts
Comment thread src/orchestrator/factory.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/state/file-state-store.ts Outdated
Comment thread src/orchestrator/factory.ts Outdated
Comment thread src/orchestrator/factory.ts
Comment thread src/orchestrator/factory.ts
Comment thread src/orchestrator/factory.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/orchestrator/factory.ts Outdated
@khaliqgant
khaliqgant force-pushed the codex/factory-273-slack-babysit-0817 branch from 7d8af75 to 3f047fe Compare August 17, 2026 13:31

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/orchestrator/factory.ts">

<violation number="1" location="src/orchestrator/factory.ts:13306">
P1: When a work unit reopens while a terminal-thread reply is still routing, this call removes the terminal fence without waiting for that route. The old reply can then be durably queued for the new dispatch; stop and drain in-flight Slack routes before clearing the terminal fence and creating the new conversation.</violation>

<violation number="2" location="src/orchestrator/factory.ts:14274">
P2: If Slack writeback is unavailable during startup, this undelivered-reply receipt aborts `#rearmSlackReplyWatchers` before it re-arms the remaining threads. Handle this terminal watch's receipt failure as retryable state maintenance and continue rehydrating other watchers.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// A reopened work unit needs a fresh dispatch notification and a fresh
// conversation. Do not let the old grace-period watcher (or its expiry
// timer) capture and later tear down the new dispatch.
await this.#stopSlackWatcher(record.issue)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: When a work unit reopens while a terminal-thread reply is still routing, this call removes the terminal fence without waiting for that route. The old reply can then be durably queued for the new dispatch; stop and drain in-flight Slack routes before clearing the terminal fence and creating the new conversation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/orchestrator/factory.ts, line 13306:

<comment>When a work unit reopens while a terminal-thread reply is still routing, this call removes the terminal fence without waiting for that route. The old reply can then be durably queued for the new dispatch; stop and drain in-flight Slack routes before clearing the terminal fence and creating the new conversation.</comment>

<file context>
@@ -13289,6 +13297,14 @@ export class FactoryLoop implements Factory {
+      // A reopened work unit needs a fresh dispatch notification and a fresh
+      // conversation. Do not let the old grace-period watcher (or its expiry
+      // timer) capture and later tear down the new dispatch.
+      await this.#stopSlackWatcher(record.issue)
+    }
     const existingThread = await this.#persistedSlackThread(key)
</file context>

this.#terminalSlackWatchIssues.add(key)
const conversationId = slackConversationId(watch.threadId)
await this.#slackConversationTurns.cancel(conversationId)
await this.#surfaceUndeliveredSlackConversation(watch.threadId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: If Slack writeback is unavailable during startup, this undelivered-reply receipt aborts #rearmSlackReplyWatchers before it re-arms the remaining threads. Handle this terminal watch's receipt failure as retryable state maintenance and continue rehydrating other watchers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/orchestrator/factory.ts, line 14274:

<comment>If Slack writeback is unavailable during startup, this undelivered-reply receipt aborts `#rearmSlackReplyWatchers` before it re-arms the remaining threads. Handle this terminal watch's receipt failure as retryable state maintenance and continue rehydrating other watchers.</comment>

<file context>
@@ -14214,6 +14255,33 @@ export class FactoryLoop implements Factory {
+        this.#terminalSlackWatchIssues.add(key)
+        const conversationId = slackConversationId(watch.threadId)
+        await this.#slackConversationTurns.cancel(conversationId)
+        await this.#surfaceUndeliveredSlackConversation(watch.threadId)
+        await this.#state.clearConversationSession(this.#workspaceId, conversationId)
+        await this.#rearmSlackWatcher(watchRecord, watch.threadId, {
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant