Skip to content

fix(mcp): a sequential chat_with_agent call that ends in a receipt reports back to the caller's conversation (#3295) - #3469

Open
vybe wants to merge 1 commit into
devfrom
feature/3295-sequential-chat-report-back
Open

vybe wants to merge 1 commit into
devfrom
feature/3295-sequential-chat-report-back

Conversation

@vybe

@vybe vybe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • A plain sequential chat_with_agent (parallel=false → POST /chat) could never post its outcome into the Slack, Telegram or Workspace thread the caller serves. When the MCP server gave up at 25 s and returned a receipt, the person heard nothing when the work finished.
  • The parent still never travels in the /chat body. The MCP server asks for the report at the one moment it knows the caller did not get the reply — when it builds a queued_timeout receipt (its own abort, a 409 in-flight replay, or the backend's own 504, now recovered through the same execution lookup) — via a new POST /api/agents/{name}/executions/{id}/report-back naming the caller's turn. A call that answers inline is never armed, so it cannot post a second "done".
  • Design choice vs the issue's AC1 (stamp at /chat row creation): stamping at creation double-posts on a pull pilot (the sink reports before the waiting handler returns the reply), and a probe on the real image showed request.is_disconnected() reads False behind the app's two @app.middleware("http") wrappers, so a "caller still waiting" hold could not be released. Recorded in docs/memory/learnings.md.

Changes

  • src/backend/services/chat_execution_service.py — arm_chat_report_back (dispatcher-of-row gate: agent key = source_agent_name, person = source_user_id, connector 403, anything else one uniform 404; /chat rows only; inheritance through the unchanged _inherited_channel_context; ent#498's add-only stamp; re-read + spawn if already terminal) and spawn_completion_report on the four push /chat terminal writes (CAS-won only).
  • src/backend/routers/chat.py, src/backend/models.py — the route and ReportBackRequest (extra=forbid, 1..128).
  • src/mcp-server/src/client.ts — armChatReportBack (own 2 s deadline, never throws; only an explicit refusal reads as off), backend 504 on /chat → the bug: chat_with_agent MCP tool returns 'fetch failed' but still queues execution — silent duplicates burn budget on naive retry #914 lookup → receipt.
  • src/mcp-server/src/tools/chat.ts — resolveReportBack defaults the chat route on under the /task rules; chatRouteFields picks result fields from the outcome; reasons answered_inline / not_armed replace sequential_chat; one clause added to EXECUTION_ID_PARAM_DESCRIPTION.
  • tests/unit/_route_census.py — the route is listed agent-callable with its reason.
  • Docs: requirements §15.1h, channel-completion-report.md (route table, new "How a sequential /chat call is armed" section, chokepoint table), api-endpoints.md, agent-to-agent-collaboration.md, user docs, learnings.md, CSO diff report.

Test Plan

Fixes #3295

🤖 Generated with Claude Code

…ports back to the caller's conversation (#3295)

A plain sequential delegation (parallel=false → POST /chat) could never post
its outcome into the Slack, Telegram or Workspace thread the caller serves:
the request had no field for the caller's turn, the row was created without
a channel context, and the /chat terminals never spawned the completion
report. When the MCP server gave up at 25 s and handed back a receipt, the
person heard nothing when the work finished.

The parent still never travels in the /chat body. The MCP server asks for
the report at the one moment it knows the caller did NOT get the reply —
when it builds a queued_timeout receipt (its own abort, a 409 in-flight
replay, or the backend's own 504, now recovered through the same execution
lookup) — through a new POST /api/agents/{name}/executions/{id}/report-back
naming the caller's turn. A call that answers inline is never armed, so it
cannot post a second "done".

Backend: arm_chat_report_back admits only the row's dispatcher (agent key =
source_agent_name, person = source_user_id; connector 403; anything else one
uniform 404), only a /chat row, and inherits through the unchanged
_inherited_channel_context provenance guard; the stamp is ent#498's add-only
stamp_execution_channel_context. The push /chat finalizers and the pull
lock-busy write spawn spawn_completion_report on a CAS-won write (a no-op
unless armed). report_completion reads the row fresh and effect_guard keys
on the destination, so stamp-then-terminal and terminal-then-stamp both
deliver exactly once.

MCP: resolveReportBack defaults the chat route on under the /task rules
(header turn first, typed id held to the header's format, manual opts out,
MCP_REPORT_BACK_ENABLED=false sends nothing); chatRouteFields picks the
result fields from the outcome — requested on an armed receipt,
off/not_armed on a refused one, off/answered_inline for a typed id whose
reply came back. The sequential_chat reason is retired.

Fixes #3295

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vybe

vybe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

merge-train (2026-10-09): ejected — the regression diff job on the current head (685470b) reports 8 new failures introduced by HEAD, including the PR's own test_3295_chat_report_back.TestDelivery cases and the existing budget-exhausted 503 path (test_1483_run_chat_and_finalize_characterization::test_budget_exhausted_maps_503_and_fails_idem, test_2433_dispatch_wiring::test_chat_budget_finalizer_*, test_ent279_secret_scrub::test_finalize_budget_exhausted_scrubs_error). A failing behaviour test needs the author's intent, not a train fix. Rides the next train once fixed.

@github-actions

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

@github-actions

Copy link
Copy Markdown

⚠️ Live-instance suite skipped — merge conflict against dev.

Resolve by merging dev locally and pushing the result; the next nightly re-tests.

@vybe

vybe commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

merge-train (2026-10-11): ejected. This PR's own regression diff (head 685470bf, run 37962621860) has 8 new failures against the cached dev baseline. None reproduces on dev:

  • test_3295_chat_report_back.TestDelivery — 4 of this PR's own new tests (test_a_failure_terminal_reports_too, test_arm_after_terminal_delivers_once, test_arm_before_terminal_leaves_the_report_to_the_terminal, test_report_completion_reads_the_row_fresh)
  • test_1483_run_chat_and_finalize_characterization::test_budget_exhausted_maps_503_and_fails_idem
  • test_2433_dispatch_wiring::test_chat_budget_finalizer_keeps_failed_503_for_a_real_exhaustion and …_writes_cancelled_and_raises_409_for_a_cancelled_park
  • test_ent279_secret_scrub.TestChatExecutionChokepoints::test_finalize_budget_exhausted_scrubs_error

The last four all go through the budget-exhaustion finalizer in chat_execution_service.py, so the change there looks like it altered that path. That needs the author's judgment rather than a train fix. The branch also conflicts with dev now. It rides the next train once it's fixed and green.

@vybe vybe added the status-needs-fix PR has an unaddressed review/validation finding; cleared by the author's next push (#2815) label Oct 11, 2026

This branch has not been deployed

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

Labels

status-needs-fix PR has an unaddressed review/validation finding; cleared by the author's next push (#2815)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant