Skip to content

fix(copilot): persist reasoning, split steps/reasoning UX, fix mid-turn promote stream stall - #12853

Merged
majdyz merged 21 commits into
devfrom
fix/copilot-queued-message-streaming
Apr 19, 2026
Merged

fix(copilot): persist reasoning, split steps/reasoning UX, fix mid-turn promote stream stall#12853
majdyz merged 21 commits into
devfrom
fix/copilot-queued-message-streaming

Conversation

@majdyz

@majdyz majdyz commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Why

Four related issues that surfaced when queued follow-ups hit an extended_thinking turn:

  1. Mid-turn promote stalled the SSE stream. pollBackendAndPromote used setMessages((prev) => [...prev, bubble]) — Vercel AI SDK's useChat streams SSE deltas into messages[-1], so once a user bubble ended up there, every subsequent chunk silently landed on the wrong message. Chat sat frozen until a page refresh, even though the backend's stream completed cleanly.
  2. Thinking-only final turn looked identical to a frozen UI. When Claude's last LLM call after a tool_result produced only a ThinkingBlock (no TextBlock, no ToolUseBlock), the response adapter silently dropped it and the UI hung on "Thought for Xs" with no response text.
  3. Reasoning was invisible. ThinkingBlock was dropped live and never persisted in a way the frontend could render — sessions on reload / shared links showed no thinking, a confusing UX gap ("display for nothing").
  4. Cross-pod Redis replay dropped reasoning events. The stream_registry._reconstruct_chunk type map had no entries for reasoning-* types, so any client that subscribed mid-stream (share, reload, cross-pod) silently dropped them with Unknown chunk type: reasoning-delta.

What

Mid-turn promote — splice before the trailing assistant

In useCopilotPendingChips.ts::pollBackendAndPromote:

setMessages((prev) => {
  const bubble = makePromotedUserBubble(drained, "midturn", crypto.randomUUID());
  const lastIdx = prev.length - 1;
  if (lastIdx >= 0 && prev[lastIdx].role === "assistant") {
    return [...prev.slice(0, lastIdx), bubble, prev[lastIdx]];
  }
  return [...prev, bubble];
});

Streaming assistant stays at messages[-1], AI SDK deltas keep routing correctly. useHydrateOnStreamEnd snaps the bubble to the DB-canonical position when the stream ends.

Reasoning — end-to-end visibility (live + persisted)

  • Wire protocol: new StreamReasoningStart / StreamReasoningDelta / StreamReasoningEnd events matching AI SDK v5's reasoning-* wire names, so useChat accumulates them into a type: 'reasoning' UIMessage part natively.
  • Response adapter: every ThinkingBlock now emits reasoning events; text/tool_use transitions close the open reasoning block so AI SDK doesn't merge distinct parts.
  • Stream registry: added reasoning-* types to _reconstruct_chunk's type_to_class map so Redis replay no longer drops them on cross-pod / reload / share.
  • Persistence (new): each StreamReasoningStart opens a ChatMessage(role="reasoning") row in session.messages; deltas accumulate into its content; StreamReasoningEnd closes it. No schema migration — ChatMessage.role is already String. extract_context_messages filters role="reasoning" out of LLM context (the --resume CLI session already carries thinking separately) so the model never re-ingests prior reasoning.
  • Frontend conversion: convertChatSessionMessagesToUiMessages maps role="reasoning" DB rows into {type: "reasoning", text} parts on the surrounding assistant bubble, so reload / shared-link sessions render reasoning identically to live stream.

Steps / Reasoning UX — modal + accordion split

  • StepsCollapse (new): a Dialog-backed "Show steps" modal wraps the pre-final-answer group (tool timeline + per-block reasoning). Modal keeps the steps visually grouped and out of the reading flow.
  • ReasoningCollapse (rewritten): inline accordion with "Show reasoning" / "Hide reasoning" toggle — no longer a modal, so it expands inside the Steps modal without stacking two dialogs. Reasoning text appears indented with a left border.
  • splitReasoningAndResponse: reasoning parts now stay in the reasoning group (instead of being pinned out), so they show up inside the Steps modal alongside the tool-use timeline.

Thinking-only final turn — synthesize a closing line (belt-and-suspenders)

  • Prompt rule (_USER_FOLLOW_UP_NOTE): "Every turn MUST end with at least one short user-facing text sentence."
  • Adapter fallback: tracks _text_since_last_tool_result; at ResultMessage success with tools run + zero text since, opens a fresh step (UserMessage already closed the previous one) and injects "(Done — no further commentary.)" before StreamFinish. Only fires for the pathological case — pure-text turns untouched.

Test plan

  • pnpm vitest run on copilot files — all 638 prior tests pass; 17 new tests added covering:
    • convertChatSessionToUiMessages: reasoning row alone / merged with assistant text / multi-row / empty skip / duration capture
    • ReasoningCollapse: initial collapsed, toggle, rotate-90, aria-expanded
    • StepsCollapse: trigger + dialog open renders children
    • MessagePartRenderer: reasoning → <pre> inside collapse, whitespace/missing text → null
    • splitReasoningAndResponse: reasoning-stays-in-reasoning regression
  • poetry run pytest backend/copilot/sdk/response_adapter_test.py — 36 pass (7 new: 4 reasoning streaming, 3 thinking-only fallback)
  • Manual: reasoning streams live and persists across reload on a fresh session
  • Manual: previously-created sessions (pre-persistence) don't have role="reasoning" rows — behaves as a clean no-op (no reasoning shown, no error), new sessions render reasoning inside Steps modal

Notes

  • No DB migration — ChatMessage.role is already an open String; role="reasoning" is simply filtered out of LLM context builds but rendered by the frontend.
  • Addresses /pr-review blockers: (a) stream_registry missing reasoning types in Redis round-trip, (b) fallback text emitted outside a step, (c) dead case "thinking" in renderer (now uses the live reasoning type uniformly).

…ng assistant

Observed on prod session 2664eff3: queued follow-up got processed
server-side (executor log: 'Persisted 1 mid-turn follow-up user row(s)',
'Stream completed successfully with 26 messages') but the client stopped
rendering SSE deltas — UI frozen until page refresh.

Root cause: ``pollBackendAndPromote`` in useCopilotPendingChips appended
the promoted user bubble via ``setMessages((prev) => [...prev, bubble])``
while the assistant was still streaming at ``messages[-1]``. The AI SDK's
``useChat`` streams every text/tool delta into the last message; once the
last message became a user bubble instead of the streaming assistant,
every subsequent chunk landed in the wrong slot (silently) and the UI
froze even though the backend kept emitting.

Fix: when the last message IS an assistant, splice the promoted bubble
at ``len-1`` so the streaming assistant stays at ``messages[-1]``. When
it's not an assistant (edge: no assistant message spawned yet),
``[...prev, bubble]`` stays safe. Added a test asserting the bubble
lands before the trailing assistant — the bug would have been caught by
this guard.
@majdyz
majdyz requested a review from a team as a code owner April 19, 2026 00:20
@majdyz
majdyz requested review from Bentlybro and Swiftyos and removed request for a team April 19, 2026 00:20
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 19, 2026
@github-actions github-actions Bot added the platform/frontend AutoGPT Platform - Front end label Apr 19, 2026
@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Promote mid-turn user bubbles before a trailing streaming assistant in the frontend; add reasoning-stream events and synthesize a fallback closing text when a tool-only turn finishes with an empty success result; update rendering and splitting to surface reasoning/thinking parts; add tests covering these behaviors and tweak the backend follow-up prompt.

Changes

Cohort / File(s) Summary
Frontend: mid-turn promotion
autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
Compute a single promoted "midturn" user bubble per promotion; if messages ends with an assistant, splice the bubble immediately before that trailing assistant instead of appending; setQueuedMessages(remaining) unchanged.
Frontend: mid-turn tests
autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
Expanded test to apply the setMessages updater and assert the promoted promoted-midturn-... message appears immediately before the trailing streaming assistant and that the assistant remains last; earlier assertions retained.
Frontend: reasoning rendering
autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/components/MessagePartRenderer.tsx
Add support for "reasoning" and "thinking" parts; render non-blank content inside ReasoningCollapse with <pre> for whitespace-preserving display.
Frontend: response splitting
autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/helpers.ts
Treat "reasoning"/"thinking" parts as pinned parts in splitReasoningAndResponse so they appear inline with other pinned parts rather than being returned separately as reasoning.
Backend: prompt text
autogpt_platform/backend/backend/copilot/prompting.py
Augmented _USER_FOLLOW_UP_NOTE to require every turn to end with at least one short, user-visible sentence (UI-facing constraint inside prompt text).
Backend: SDK adapter (streaming)
autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
Add reasoning stream events (start/delta/end) for ThinkingBlock content and lifecycle tracking; track _text_since_last_tool_result and _any_tool_results_seen; close reasoning/text when appropriate; on ResultMessage(subtype="success") after tool results with no subsequent emitted text, synthesize a StreamTextDelta("(Done — no further commentary.)") before finishing.
Backend: response model
autogpt_platform/backend/backend/copilot/response_model.py
Added ResponseType entries reasoning-start, reasoning-delta, reasoning-end and new Pydantic stream classes StreamReasoningStart, StreamReasoningDelta(delta: str), StreamReasoningEnd.
Backend tests
autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
Added tests for reasoning-streaming lifecycle and for fallback synthesized text when a tool-only final turn yields an empty successful result; cover closing behavior and guards for empty thinking blocks.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SDK as SDKResponseAdapter
    participant Tool
    participant Stream as StreamOutput

    Client->>SDK: emit ToolUseBlock / invoke tool
    SDK->>Tool: call tool
    Tool-->>SDK: ToolResult(s)
    SDK->>Stream: emit StreamTextDelta(s) for tool results
    alt No assistant/text after tool result and ResultMessage(subtype="success") arrives
        SDK->>SDK: _any_tool_results_seen == true && _text_since_last_tool_result == false
        SDK->>Stream: emit StreamTextDelta("(Done — no further commentary.)")
        SDK->>Stream: emit StreamFinish
    else Assistant text or prior text emitted after tool results
        SDK->>Stream: emit StreamFinish (no synthesized text)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Swiftyos
  • Bentlybro
  • Pwuts

Poem

🐇 A bubble hops in, finds its place before the stream,

Thoughts whisper out as reasoning in a gentle beam.
The backend signs off with one last friendly line,
Tests clap their paws — the flows now align.
🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main fixes: mid-turn promote stream stall, reasoning persistence, and steps/reasoning UX split.
Description check ✅ Passed The description comprehensively explains all changes, root causes, and solutions for the mid-turn promote stall, reasoning visibility, and thinking-only final turn issues.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/copilot-queued-message-streaming

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 and usage tips.

@codecov

codecov Bot commented Apr 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.42515% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.84%. Comparing base (b1c043c) to head (aa06b90).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #12853      +/-   ##
==========================================
+ Coverage   65.74%   65.84%   +0.09%     
==========================================
  Files        1864     1865       +1     
  Lines      139528   139662     +134     
  Branches    14933    14949      +16     
==========================================
+ Hits        91739    91963     +224     
+ Misses      44966    44867      -99     
- Partials     2823     2832       +9     
Flag Coverage Δ
platform-backend 76.24% <87.85%> (+0.01%) ⬆️
platform-frontend 21.67% <95.83%> (+0.20%) ⬆️
platform-frontend-e2e 30.82% <0.00%> (+0.95%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 76.24% <87.85%> (+0.01%) ⬆️
Platform Frontend 29.32% <85.18%> (+0.43%) ⬆️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/__tests__/useCopilotPendingChips.test.ts:
- Around line 349-354: The test comment in useCopilotPendingChips.test.ts
contains a concrete production session ID ("2664eff3"); remove or redact that
identifier from the comment (e.g., delete the numeric ID or replace with a
generic placeholder like "<prod session id>") in the explanatory block that
follows the explanation about message ordering and SSE streaming so the comment
still explains the bug without committing PII/session identifiers.

In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/useCopilotPendingChips.ts:
- Around line 284-286: Remove the concrete production session ID from the inline
comment in useCopilotPendingChips.ts (the comment block inside the
useCopilotPendingChips code path); replace the specific ID "2664eff3" with a
generic phrase such as "a production session" or "a private incident" so the
comment keeps context without exposing a potentially sensitive session
identifier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ea0a19f-6af5-4dee-8d2e-30cc533fb862

📥 Commits

Reviewing files that changed from the base of the PR and between b1c043c and 660dde2.

📒 Files selected for processing (2)
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: integration_test
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: end-to-end tests
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (10)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend development

Format frontend code using pnpm format

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/generated/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/
'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

autogpt_platform/frontend/**/*.{ts,tsx}: Fully capitalize acronyms in symbols, e.g. graphID, useBackendAPI
Use function declarations (not arrow functions) for components and handlers
No dark: Tailwind classes — the design system handles dark mode
Use Next.js <Link> for internal navigation — never raw <a> tags
No any types unless the value genuinely can be anything
No linter suppressors (// @ts-ignore``, // eslint-disable) — fix the actual issue
Keep files under ~200 lines; extract sub-components or hooks into their own files when a file grows beyond this
Keep render functions and hooks under ~50 lines; extract named helpers or sub-components when they grow longer
Use generated API hooks from `@/app/api/generated/endpoints/` with pattern `use{Method}{Version}{OperationName}` and regenerate with `pnpm generate:api`
Do not use `useCallback` or `useMemo` unless asked to optimise a given function
Separate render logic (`.tsx`) from business logic (`use*.ts` hooks)
Use ErrorCard for render errors, toast for mutations, and Sentry for exceptions in the frontend

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/src/**/*.{ts,tsx}: Use generated API hooks from @/app/api/__generated__/endpoints/ following the pattern use{Method}{Version}{OperationName}, and regenerate with pnpm generate:api
Separate render logic from business logic using component.tsx + useComponent.ts + helpers.ts pattern, colocate state when possible and avoid creating large components, use sub-components in local /components folder
Use function declarations for components and handlers, use arrow functions only for callbacks
Do not use useCallback or useMemo unless asked to optimise a given function

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

No barrel files or index.ts re-exports in the frontend

Do not type hook returns, let Typescript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not type hook returns, let Typescript infer as much as possible

Extract component logic into custom hooks grouped by concern, not by component. Each hook should represent a cohesive domain of functionality (e.g., useSearch, useFilters, usePagination) rather than bundling all state into one useComponentState hook. Put each hook in its own .ts file.

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never type with any, if no types available use unknown

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}: Use Vitest + RTL + MSW for integration tests as the primary testing approach (~90%, page-level), use Playwright for E2E critical flows, and use Storybook for design system components
Run frontend integration tests with pnpm test:unit (Vitest + RTL + MSW)

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/src/app/(platform)/**/__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)

Write integration tests in __tests__/ next to page.tsx using Vitest + RTL + MSW for new pages/features

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
autogpt_platform/frontend/src/**/__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)

Use Orval-generated MSW handlers from @/app/api/__generated__/endpoints/{tag}/{tag}.msw.ts for API mocking

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
🧠 Learnings (11)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12797
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1991-2021
Timestamp: 2026-04-15T13:44:34.273Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/service.py` (`_run_stream_attempt`), the pre-create block (PR `#12797`) intentionally does NOT call `state.transcript_builder.append_assistant(...)` when inserting the empty assistant placeholder into `ctx.session.messages`. The transcript is left ending at the `tool_result` entry (N entries) while `message_count` metadata is N+1. This mismatch is benign and deliberate: on the next `--resume`, the SDK sees the transcript ending at `tool_result` and correctly regenerates the assistant response. Pre-appending the assistant turn to the transcript would suppress regeneration while leaving `session.messages[-1].content = ""` permanently (worse outcome). On the gap-fallback path, `transcript_msg_count (N+1) >= msg_count-1 (N)` means no gap is injected for the empty placeholder, which is correct because injecting an empty assistant message as context would mislead the SDK. Do NOT flag this transcript/message_count discrepancy as a bug.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/baseline/service.py:0-0
Timestamp: 2026-04-03T11:14:45.569Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/service.py`, `transcript_builder.append_user(content=message)` is called unconditionally even when the message is a duplicate that was suppressed by the `is_new_message` guard. This is intentional: the downloaded transcript may be stale (uploaded before the previous attempt persisted the message), so always appending the current user turn prevents a malformed assistant-after-assistant transcript structure. The `is_user_message` flag is still checked (`if message and is_user_message:`), so assistant-role inputs are excluded. Do NOT flag this as a bug.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/frontend/src/app/api/openapi.json:12803-12806
Timestamp: 2026-04-14T06:39:52.592Z
Learning: Repo: Significant-Gravitas/AutoGPT — autogpt_platform
Intentional message length caps:
- StreamChatRequest.message maxLength = 64000.
- QueuePendingMessageRequest.message maxLength = 32000 (matches PendingMessage.content).
Rationale: both feed the same LLM context window; pending must not exceed stream, and larger ceilings replace legacy 4000/16000.
Learnt from: kcze
Repo: Significant-Gravitas/AutoGPT PR: 12328
File: autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts:49-61
Timestamp: 2026-03-11T08:40:59.673Z
Learning: In `autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts`, clearing `olderMessages` (and resetting `oldestSequence`/`hasMore`) when `initialOldestSequence` shifts on the same session is intentional. Pages already fetched were based on a now-stale cursor; retaining them risks sequence gaps or duplicates. `ScrollPreserver` keeps the currently visible viewport intact, so only unvisited older pages are dropped. This is a deliberate safe-refetch design tradeoff.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12574
File: autogpt_platform/backend/backend/copilot/sdk/transcript.py:980-990
Timestamp: 2026-03-26T07:00:03.405Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/transcript.py`, `_rechain_tail` intentionally rewrites `parentUuid` for **all** tail entries (not just the first), because a single assistant turn can span multiple consecutive JSONL entries sharing the same `message.id` (e.g., a thinking entry + a tool_use entry). Their original `parentUuid` values may reference entries that were absorbed into the compressed prefix, so sequential rechaining of the entire tail is required to maintain a valid parent→child graph. The test `test_chains_multiple_tail_entries` validates this: the second tail entry's `parentUuid` is rewritten from its original value to the uuid of the first tail entry.
📚 Learning: 2026-04-14T14:36:25.545Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-15T13:44:34.273Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12797
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1991-2021
Timestamp: 2026-04-15T13:44:34.273Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/service.py` (`_run_stream_attempt`), the pre-create block (PR `#12797`) intentionally does NOT call `state.transcript_builder.append_assistant(...)` when inserting the empty assistant placeholder into `ctx.session.messages`. The transcript is left ending at the `tool_result` entry (N entries) while `message_count` metadata is N+1. This mismatch is benign and deliberate: on the next `--resume`, the SDK sees the transcript ending at `tool_result` and correctly regenerates the assistant response. Pre-appending the assistant turn to the transcript would suppress regeneration while leaving `session.messages[-1].content = ""` permanently (worse outcome). On the gap-fallback path, `transcript_msg_count (N+1) >= msg_count-1 (N)` means no gap is injected for the empty placeholder, which is correct because injecting an empty assistant message as context would mislead the SDK. Do NOT flag this transcript/message_count discrepancy as a bug.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-03T11:14:45.569Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/baseline/service.py:0-0
Timestamp: 2026-04-03T11:14:45.569Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/service.py`, `transcript_builder.append_user(content=message)` is called unconditionally even when the message is a duplicate that was suppressed by the `is_new_message` guard. This is intentional: the downloaded transcript may be stale (uploaded before the previous attempt persisted the message), so always appending the current user turn prevents a malformed assistant-after-assistant transcript structure. The `is_user_message` flag is still checked (`if message and is_user_message:`), so assistant-role inputs are excluded. Do NOT flag this as a bug.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-03-11T08:40:59.673Z
Learnt from: kcze
Repo: Significant-Gravitas/AutoGPT PR: 12328
File: autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts:49-61
Timestamp: 2026-03-11T08:40:59.673Z
Learning: In `autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts`, clearing `olderMessages` (and resetting `oldestSequence`/`hasMore`) when `initialOldestSequence` shifts on the same session is intentional. Pages already fetched were based on a now-stale cursor; retaining them risks sequence gaps or duplicates. `ScrollPreserver` keeps the currently visible viewport intact, so only unvisited older pages are dropped. This is a deliberate safe-refetch design tradeoff.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-03-24T02:23:31.305Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12526
File: autogpt_platform/frontend/src/app/(platform)/copilot/components/RateLimitResetDialog/RateLimitResetDialog.tsx:0-0
Timestamp: 2026-03-24T02:23:31.305Z
Learning: In the Copilot platform UI code, follow the established Orval hook `onError` error-handling convention: first explicitly detect/handle `ApiError`, then read `error.response?.detail` (if present) as the primary message; if not available, fall back to `error.message`; and finally fall back to a generic string message. This convention should be used for generated Orval hooks even if the custom Orval mutator already maps details into `ApiError.message`, to keep consistency across hooks/components (e.g., `useCronSchedulerDialog.ts`, `useRunGraph.ts`, and rate-limit/reset flows).

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-01T18:54:16.035Z
Learnt from: Bentlybro
Repo: Significant-Gravitas/AutoGPT PR: 12633
File: autogpt_platform/frontend/src/app/(platform)/library/components/AgentFilterMenu/AgentFilterMenu.tsx:3-10
Timestamp: 2026-04-01T18:54:16.035Z
Learning: In the frontend, the legacy Select component at `@/components/__legacy__/ui/select` is an intentional, codebase-wide visual-consistency pattern. During code reviews, do not flag or block PRs merely for continuing to use this legacy Select. If a migration to the newer design-system Select is desired, bundle it into a single dedicated cleanup/migration PR that updates all Select usages together (e.g., avoid piecemeal replacements).

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-07T09:24:16.582Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12686
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/__tests__/PainPointsStep.test.tsx:1-19
Timestamp: 2026-04-07T09:24:16.582Z
Learning: In Significant-Gravitas/AutoGPT’s `autogpt_platform/frontend` (Vite + `vitejs/plugin-react` with the automatic JSX transform), do not flag usages of React types/components (e.g., `React.ReactNode`) in `.ts`/`.tsx` files as missing `React` imports. Since the React namespace is made available by the project’s TS/Vite setup, an explicit `import React from 'react'` or `import type { ReactNode } ...` is not required; only treat it as missing if typechecking (e.g., `pnpm types`) would actually fail.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-02T05:43:49.128Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12640
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/WelcomeStep.tsx:13-13
Timestamp: 2026-04-02T05:43:49.128Z
Learning: Do not flag `import { Question } from "phosphor-icons/react"` as an invalid import. `Question` is a valid named export from `phosphor-icons/react` (as reflected in the package’s generated `.d.ts` files and re-exports via `dist/index.d.ts`), so it should be treated as a supported named export during code reviews.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-03-26T07:00:03.405Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12574
File: autogpt_platform/backend/backend/copilot/sdk/transcript.py:980-990
Timestamp: 2026-03-26T07:00:03.405Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/transcript.py`, `_rechain_tail` intentionally rewrites `parentUuid` for **all** tail entries (not just the first), because a single assistant turn can span multiple consecutive JSONL entries sharing the same `message.id` (e.g., a thinking entry + a tool_use entry). Their original `parentUuid` values may reference entries that were absorbed into the compressed prefix, so sequential rechaining of the entire tail is required to maintain a valid parent→child graph. The test `test_chains_multiple_tail_entries` validates this: the second tail entry's `parentUuid` is rewritten from its original value to the uuid of the first tail entry.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
📚 Learning: 2026-04-15T14:10:18.177Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/backend/copilot/graphiti/CLAUDE.md:0-0
Timestamp: 2026-04-15T14:10:18.177Z
Learning: Applies to autogpt_platform/backend/backend/copilot/graphiti/**/*agent*.{ts,tsx} : Use dependency injection for agent dependencies to improve testability

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts
🔇 Additional comments (2)
autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts (1)

288-299: Good fix: the streaming assistant remains the tail message.

The updater now inserts the promoted mid-turn user bubble before a trailing assistant, preserving the messages[-1] invariant used by the streaming/rendering paths, while still appending in the no-assistant edge case.

autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useCopilotPendingChips.test.ts (1)

355-366: Good regression coverage for the ordering invariant.

Applying the captured updater to [user, assistant] and asserting the assistant remains last directly protects the SSE streaming failure mode.

Comment thread autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPendingChips.ts Outdated
majdyz added 3 commits April 19, 2026 08:02
When extended_thinking mode emits an AssistantMessage with only a
ThinkingBlock (no TextBlock, no ToolUseBlock) as the final turn after
tool_results, the response adapter silently drops it — the UI is left
with the last tool output and a "Thought for Xs" label and appears
stuck, even though the backend's Stream completed successfully.
Observed in prod when long find_block results (~85K chars) came back
and Claude decided to end without closing text.

Two layers of defence:

1. **Prompt rule** — add a "Always close the turn with visible text"
   clause to the SDK system supplement so the model knows its turn must
   end with text, not just thinking or tool_use.

2. **Adapter fallback** — track whether a TextBlock was emitted since
   the most recent tool_result; at ResultMessage time, if we've seen
   tool_results but zero TextBlocks after them and the outcome is
   success, synthesize a short "(Done — no further commentary.)" text
   before StreamFinish.  The UI renders a proper assistant-text bubble
   and the turn visibly completes.

Pure-text turns (no tool_results) are untouched — they produce text
through normal AssistantMessage handling.  Tests cover the three cases:
fallback fires / doesn't fire when text was emitted / doesn't fire
when no tools ran.
@github-actions github-actions Bot added platform/backend AutoGPT Platform - Back end size/l and removed size/m labels Apr 19, 2026
@github-actions

github-actions Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

  • fix(frontend/copilot): fix streaming reconnect races, hydration ordering, and reasoning split #12813 (0ubbe · updated 1d ago)

    • 📁 autogpt_platform/frontend/src/app/(platform)/copilot/
      • CopilotPage.tsx (1 conflict, ~20 lines)
      • __tests__/useCopilotPage.test.ts (2 conflicts, ~121 lines)
      • __tests__/useLoadMoreMessages.test.ts (1 conflict, ~63 lines)
      • components/ChatMessagesContainer/helpers.test.ts (2 conflicts, ~264 lines)
      • useCopilotPage.ts (4 conflicts, ~330 lines)
      • useCopilotStream.ts (1 conflict, ~40 lines)
  • fix(copilot): prevent 524 timeout on chat deletion by deferring cleanup #12668 (Otto-AGPT · updated 2d ago)

    • autogpt_platform/backend/backend/api/features/chat/routes_test.py (1 conflict, ~285 lines)
    • autogpt_platform/backend/backend/api/features/library/db.py (5 conflicts, ~67 lines)
    • autogpt_platform/backend/backend/api/features/library/model.py (1 conflict, ~4 lines)
    • autogpt_platform/backend/backend/copilot/baseline/service.py (2 conflicts, ~15 lines)
    • autogpt_platform/backend/backend/copilot/model_test.py (1 conflict, ~5 lines)
    • autogpt_platform/backend/backend/copilot/sdk/service.py (2 conflicts, ~30 lines)
    • autogpt_platform/backend/backend/copilot/transcript.py (1 conflict, ~11 lines)
    • autogpt_platform/frontend/src/app/(platform)/copilot/components/PulseChips/usePulseChips.ts (1 conflict, ~13 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/AgentBriefingPanel/BriefingTabContent.tsx (2 conflicts, ~22 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/ContextualActionButton/ContextualActionButton.tsx (2 conflicts, ~12 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/SitrepItem/SitrepItem.tsx (2 conflicts, ~15 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/SitrepItem/useSitrepItems.ts (4 conflicts, ~97 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/hooks/useAgentStatus.ts (2 conflicts, ~10 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/hooks/useLibraryFleetSummary.ts (1 conflict, ~5 lines)
    • docs/integrations/block-integrations/misc.md (1 conflict, ~5 lines)
  • feat(copilot): add goal decomposition step before agent building #12731 (anvyle · updated 11h ago)

    • 📁 autogpt_platform/
      • backend/backend/copilot/tools/create_agent.py (2 conflicts, ~29 lines)
      • frontend/src/app/api/openapi.json (3 conflicts, ~24 lines)
  • fix(backend/copilot): reuse existing credentials across chat sessions #12767 (0ubbe · updated 2d ago)

    • 📁 autogpt_platform/backend/backend/copilot/
      • service.py (1 conflict, ~6 lines)
  • Persist stable copilot message IDs through hydration #12676 (rotempasharel1 · updated 5d ago)

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 5 conflict(s), 0 medium risk, 9 low risk (out of 14 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
autogpt_platform/backend/backend/copilot/sdk/response_adapter.py (1)

249-260: Consider extracting the fallback text as a module-level constant.

The synthesized closing line "(Done — no further commentary.)" is user-visible and likely to be tweaked (wording, i18n) independently of the logic. Pulling it into a named constant at the top of the module keeps the control flow here focused on when to emit and makes future edits trivial to find via rg.

♻️ Suggested refactor
+# Fallback line synthesised when the model's final turn after a tool_result
+# produced only a ThinkingBlock — prevents the UI from hanging with no
+# visible response text.
+_THINKING_ONLY_FALLBACK_TEXT = "(Done — no further commentary.)"
+
@@
             if (
                 self._any_tool_results_seen
                 and not self._text_since_last_tool_result
                 and sdk_message.subtype == "success"
             ):
                 self._ensure_text_started(responses)
                 responses.append(
                     StreamTextDelta(
                         id=self.text_block_id,
-                        delta="(Done — no further commentary.)",
+                        delta=_THINKING_ONLY_FALLBACK_TEXT,
                     )
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/sdk/response_adapter.py` around
lines 249 - 260, The user-visible fallback string "(Done — no further
commentary.)" should be extracted to a module-level named constant so
wording/i18n changes are easy to find; create a constant (e.g.
DONE_NO_COMMENTARY = "(Done — no further commentary.)") at the top of the module
and replace the inline literal in the StreamTextDelta creation inside the block
that checks self._any_tool_results_seen and not
self._text_since_last_tool_result and sdk_message.subtype == "success"
(references: StreamTextDelta, self.text_block_id) so the control flow remains
focused on when to emit the message and the text can be updated centrally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@autogpt_platform/backend/backend/copilot/sdk/response_adapter.py`:
- Around line 249-260: The user-visible fallback string "(Done — no further
commentary.)" should be extracted to a module-level named constant so
wording/i18n changes are easy to find; create a constant (e.g.
DONE_NO_COMMENTARY = "(Done — no further commentary.)") at the top of the module
and replace the inline literal in the StreamTextDelta creation inside the block
that checks self._any_tool_results_seen and not
self._text_since_last_tool_result and sdk_message.subtype == "success"
(references: StreamTextDelta, self.text_block_id) so the control flow remains
focused on when to emit the message and the text can be updated centrally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9d59d3a-e07e-4c58-a78c-c40363edaf1b

📥 Commits

Reviewing files that changed from the base of the PR and between fd3391e and b2eaa62.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/copilot/prompting.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
✅ Files skipped from review due to trivial changes (1)
  • autogpt_platform/backend/backend/copilot/prompting.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: check API types
  • GitHub Check: integration_test
  • GitHub Check: Seer Code Review
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (typescript)
🧰 Additional context used
📓 Path-based instructions (3)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
🧠 Learnings (16)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12797
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1991-2021
Timestamp: 2026-04-15T13:44:34.273Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/service.py` (`_run_stream_attempt`), the pre-create block (PR `#12797`) intentionally does NOT call `state.transcript_builder.append_assistant(...)` when inserting the empty assistant placeholder into `ctx.session.messages`. The transcript is left ending at the `tool_result` entry (N entries) while `message_count` metadata is N+1. This mismatch is benign and deliberate: on the next `--resume`, the SDK sees the transcript ending at `tool_result` and correctly regenerates the assistant response. Pre-appending the assistant turn to the transcript would suppress regeneration while leaving `session.messages[-1].content = ""` permanently (worse outcome). On the gap-fallback path, `transcript_msg_count (N+1) >= msg_count-1 (N)` means no gap is injected for the empty placeholder, which is correct because injecting an empty assistant message as context would mislead the SDK. Do NOT flag this transcript/message_count discrepancy as a bug.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/baseline/service.py:0-0
Timestamp: 2026-04-03T11:14:45.569Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/service.py`, `transcript_builder.append_user(content=message)` is called unconditionally even when the message is a duplicate that was suppressed by the `is_new_message` guard. This is intentional: the downloaded transcript may be stale (uploaded before the previous attempt persisted the message), so always appending the current user turn prevents a malformed assistant-after-assistant transcript structure. The `is_user_message` flag is still checked (`if message and is_user_message:`), so assistant-role inputs are excluded. Do NOT flag this as a bug.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/frontend/src/app/api/openapi.json:12803-12806
Timestamp: 2026-04-14T06:39:52.592Z
Learning: Repo: Significant-Gravitas/AutoGPT — autogpt_platform
Intentional message length caps:
- StreamChatRequest.message maxLength = 64000.
- QueuePendingMessageRequest.message maxLength = 32000 (matches PendingMessage.content).
Rationale: both feed the same LLM context window; pending must not exceed stream, and larger ceilings replace legacy 4000/16000.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/blocks/autopilot.py:631-638
Timestamp: 2026-04-14T07:35:11.464Z
Learning: In `autogpt_platform/backend/backend/copilot/executor/utils.py`, `CoPilotExecutionEntry` includes a `permissions: CopilotPermissions | None` field (added in PR `#12773` / commit a0184c87b9). `enqueue_copilot_turn` accepts and serializes this field into the queue entry, `_enqueue_for_recovery` in `autopilot.py` accepts and forwards `permissions` to `enqueue_copilot_turn`, and `_execute_async` in `processor.py` restores `entry.permissions` and passes it into `stream_chat_completion_sdk`/`stream_chat_completion_baseline` via `set_execution_context`. This ensures recovered sub-agent turns respect the same tool/block permission ceiling as the original in-process execution (mirroring `_merge_inherited_permissions`). Do NOT flag recovered turns as losing their permission ceiling — it is now fully propagated through the queue.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:22.025Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.
Learnt from: kcze
Repo: Significant-Gravitas/AutoGPT PR: 12328
File: autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts:49-61
Timestamp: 2026-03-11T08:40:59.673Z
Learning: In `autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts`, clearing `olderMessages` (and resetting `oldestSequence`/`hasMore`) when `initialOldestSequence` shifts on the same session is intentional. Pages already fetched were based on a now-stale cursor; retaining them risks sequence gaps or duplicates. `ScrollPreserver` keeps the currently visible viewport intact, so only unvisited older pages are dropped. This is a deliberate safe-refetch design tradeoff.
📚 Learning: 2026-04-15T13:44:34.273Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12797
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1991-2021
Timestamp: 2026-04-15T13:44:34.273Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/service.py` (`_run_stream_attempt`), the pre-create block (PR `#12797`) intentionally does NOT call `state.transcript_builder.append_assistant(...)` when inserting the empty assistant placeholder into `ctx.session.messages`. The transcript is left ending at the `tool_result` entry (N entries) while `message_count` metadata is N+1. This mismatch is benign and deliberate: on the next `--resume`, the SDK sees the transcript ending at `tool_result` and correctly regenerates the assistant response. Pre-appending the assistant turn to the transcript would suppress regeneration while leaving `session.messages[-1].content = ""` permanently (worse outcome). On the gap-fallback path, `transcript_msg_count (N+1) >= msg_count-1 (N)` means no gap is injected for the empty placeholder, which is correct because injecting an empty assistant message as context would mislead the SDK. Do NOT flag this transcript/message_count discrepancy as a bug.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-13T14:19:19.341Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12740
File: autogpt_platform/frontend/src/app/api/openapi.json:0-0
Timestamp: 2026-04-13T14:19:19.341Z
Learning: Repo: Significant-Gravitas/AutoGPT — autogpt_platform
When adding new CoPilot tool response models (e.g., ScheduleListResponse, ScheduleDeletedResponse), update backend/api/features/chat/routes.py to include them in the ToolResponseUnion so the frontend’s autogenerated openapi.json dummy export (/api/chat/schema/tool-responses) exposes them for codegen. Do not hand-edit frontend/src/app/api/openapi.json.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-17T10:57:12.953Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-26T07:00:03.405Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12574
File: autogpt_platform/backend/backend/copilot/sdk/transcript.py:980-990
Timestamp: 2026-03-26T07:00:03.405Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/transcript.py`, `_rechain_tail` intentionally rewrites `parentUuid` for **all** tail entries (not just the first), because a single assistant turn can span multiple consecutive JSONL entries sharing the same `message.id` (e.g., a thinking entry + a tool_use entry). Their original `parentUuid` values may reference entries that were absorbed into the compressed prefix, so sequential rechaining of the entire tail is required to maintain a valid parent→child graph. The test `test_chains_multiple_tail_entries` validates this: the second tail entry's `parentUuid` is rewritten from its original value to the uuid of the first tail entry.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py
  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-03T11:14:45.569Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/baseline/service.py:0-0
Timestamp: 2026-04-03T11:14:45.569Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/service.py`, `transcript_builder.append_user(content=message)` is called unconditionally even when the message is a duplicate that was suppressed by the `is_new_message` guard. This is intentional: the downloaded transcript may be stale (uploaded before the previous attempt persisted the message), so always appending the current user turn prevents a malformed assistant-after-assistant transcript structure. The `is_user_message` flag is still checked (`if message and is_user_message:`), so assistant-role inputs are excluded. Do NOT flag this as a bug.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-03-17T06:48:26.471Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-03T11:14:16.378Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/transcript_builder.py:30-34
Timestamp: 2026-04-03T11:14:16.378Z
Learning: In `autogpt_platform/backend/backend/copilot/transcript_builder.py` (and its re-export shim at `sdk/transcript_builder.py`), `TranscriptEntry.parentUuid` is typed `str` (not `str | None`) and root entries use `parentUuid=""` (empty string) to match the canonical `_messages_to_transcript` JSONL format. `_parse_entry`, `append_user`, and `append_assistant` all coerce `None` to `""`. Do NOT flag `parentUuid=""` as incorrect — it is the correct root marker. This was fixed in PR `#12623`, commit b753cb7d0b.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
📚 Learning: 2026-04-14T14:36:25.545Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
🔇 Additional comments (2)
autogpt_platform/backend/backend/copilot/sdk/response_adapter.py (1)

62-70: State tracking looks correct.

Flag lifecycle is consistent with the fallback trigger at the ResultMessage branch:

  • Set _text_since_last_tool_result = True only when block.text is non-empty (line 114 guard).
  • Reset on any newly-resolved tool result (covers both ToolResultBlock and the parent_tool_use_id built-in tool path since both funnel through resolved_in_blocks).
  • _any_tool_results_seen is intentionally sticky for the lifetime of the adapter (per-stream), so a late tool-less tail still benefits from the guard.

One small behavioural note: a whitespace-only TextBlock (unlikely but possible) would set the flag True and suppress the fallback. If that matters, tighten to if block.text and block.text.strip():. Not a blocker.

Also applies to: 119-119, 223-229

autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py (1)

254-365: Solid coverage of the fallback paths.

The three tests cleanly exercise the state machine: positive (thinking-only tail after tool_result → exactly one synthesized non-empty StreamTextDelta), negative-with-text (explicit closing AssistantMessage suppresses synthesis), and negative-no-tools (pure text turn, sticky _any_tool_results_seen=False). The strip() check in the positive case also guards against accidental whitespace-only regressions.

Claude's ``ThinkingBlock`` content was dropped at the response adapter
layer and the frontend had no handler for the ``type: 'thinking'`` parts
we already persist to ``session.messages`` — so reasoning was invisible
both live and on reload.  When the model's final turn after a
tool_result was thinking-only, the UI stalled on "Thought for Xs" with
no visible response even though the backend's stream completed cleanly.

End-to-end reasoning visibility:

* New ``StreamReasoningStart`` / ``StreamReasoningDelta`` /
  ``StreamReasoningEnd`` events on the wire, matching the AI SDK v5
  reasoning part protocol (``reasoning-start`` etc.) so ``useChat``
  accumulates them into a ``type: 'reasoning'`` UIMessage part.
* ``SDKResponseAdapter`` emits reasoning events for every
  ``ThinkingBlock``; text/tool-use transitions close the open
  reasoning block so the AI SDK transport keeps distinct parts.
* ``MessagePartRenderer`` gains a case for both ``'reasoning'`` (live)
  and ``'thinking'`` (persisted-from-DB) — both render via
  ``ReasoningCollapse`` (default collapsed, click to expand).  The
  persisted-path coverage means every prior session with thinking
  content in the DB immediately starts showing the reasoning on
  reload or share, no backfill required.
* ``splitReasoningAndResponse`` keeps reasoning / thinking parts out
  of the outer preamble collapse so the two triggers don't nest.
@majdyz majdyz changed the title fix(frontend/copilot): stop mid-turn promote from stalling the SSE stream fix(copilot): stream reasoning, synthesize closing text, fix mid-turn promote stream stall Apr 19, 2026
Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
Comment thread autogpt_platform/backend/backend/copilot/response_model.py
Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter_test.py Outdated
@github-actions github-actions Bot added size/xl and removed size/l labels Apr 19, 2026
majdyz added 2 commits April 19, 2026 09:25
…hinking case

- response_adapter: when ResultMessage triggers the thinking-only
  fallback synthesis, the preceding UserMessage has already closed
  the step.  Open a new step before emitting the synthesized text
  delta so the AI SDK v5 transport accepts it (text-delta chunks
  must be wrapped in start-step / finish-step).
- MessagePartRenderer: drop the ``case "thinking"`` branch.  There is
  no code path that produces ``type: "thinking"`` parts — live stream
  events emit ``reasoning-*`` (→ ``type: "reasoning"``) and persisted
  rows now flow through role=``reasoning`` → ``type: "reasoning"`` via
  ``convertChatSessionMessagesToUiMessages``, so the thinking branch
  was dead.
@majdyz majdyz changed the title fix(copilot): stream reasoning, synthesize closing text, fix mid-turn promote stream stall fix(copilot): persist reasoning, split steps/reasoning UX, fix mid-turn promote stream stall Apr 19, 2026
majdyz added 2 commits April 19, 2026 09:39
GitHub's per-user GraphQL rate limit trips distinctly from REST's
primary and secondary limits, and it's surprisingly easy to hit when
paginating review threads on a busy PR. Add coverage so future runs
don't grind to a halt when GraphQL is unavailable (either
rate-limited or fully down):

- PR metadata reads (title/body/baseRef/mergeable) fall back to REST
  `gh api repos/.../pulls/{N}`; flag the `null` <-> `UNKNOWN`
  mergeable mapping.
- Inline comments fall back to REST `/pulls/{N}/comments` — degraded
  (no thread grouping, no `isResolved`, no Relay IDs) but enough to
  read + reply.
- Top-level reviews, conversation comments, and reply posting are
  already REST-native — call that out so future runs don't assume
  the whole flow is blocked.
- `resolveReviewThread` has no REST equivalent — document the
  queue-and-retry pattern.
- Add detection snippet (`gh api rate_limit --jq '.resources.graphql'`
  stays REST) and a "keeps working / degraded / blocked" matrix so
  the agent knows what to try before giving up.

(cherry picked from commit 643dd4e513f21878ae9c4ed4a1db568872906fa6)
…uard

coderabbitai flagged that `_flush_unresolved_tool_calls` emitted
`StreamToolOutputAvailable` but never updated `_any_tool_results_seen`
or `_text_since_last_tool_result`, so a turn whose ONLY tool outputs
were flushed (SDK built-ins like WebSearch) could slip past the
thinking-only fallback synthesizer.

Mirror the UserMessage handler's tracking: after a successful flush
set `_any_tool_results_seen = True` and reset
`_text_since_last_tool_result = False`. Existing flush test updated
to expect the synthesized closing text, and the stale test comment
(pre-reasoning-streaming) refreshed.
Comment thread autogpt_platform/backend/backend/copilot/transcript.py
majdyz added 3 commits April 19, 2026 09:46
…tion

Previous edit scattered the same rate-limit guidance across six
sections with repeated 10-line blockquotes. Move the details into
the "GitHub rate limits" section under clear subheadings (Detection /
What keeps working / Fall back to REST / Recovery from 403 abuse) and
replace each scattered blockquote with a single-line cross-reference.
sentry[bot] caught that `_jsonl_covered = len(session.messages)`
double-counts once role="reasoning" rows are persisted: the CLI JSONL
stores extended_thinking embedded inside assistant entries, never as
standalone rows, so its length is strictly smaller than
session.messages post-persistence. The inflated watermark makes
detect_gap on the next turn skip real user/assistant rows.

Count non-reasoning rows only for the watermark.
sentry[bot] flagged that sequence-less messages collide on
`{sessionId}-seq-null` as a React key. In the current code path the
converter only sees DB rows (which always carry a sequence), but add
a loop-index fallback so future regressions (a code path that
forwards streaming messages through the converter without
sequencing) don't silently duplicate keys.
Comment thread autogpt_platform/backend/backend/copilot/transcript.py
majdyz added 3 commits April 19, 2026 09:58
Companion to the watermark fix (af60013).  sentry flagged a follow-on
index misalignment: once _jsonl_covered counts non-reasoning rows, the
prior[transcript_msg_count - 1] watermark-alignment check inside
_build_query_message tripped because prior still contained reasoning
rows.  On a turn that followed a reasoning-emitting turn, this caused
the check to fall on a reasoning row (role != "assistant") and the
entire gap injection to be skipped — dropping mid-turn user rows from
the next LLM query.

Filter prior the same way extract_context_messages already does so the
watermark and the slice agree on the same row set.
The balanced/advanced toggle used to only apply to the SDK path — in
fast mode the model was picked by CopilotMode, not by the tier. That
split made "Fast + Advanced" a no-op, and meant advanced was
hardcoded to opus-4-6 in one spot while Sonnet lived in config.

Unify:
- Add `CHAT_ADVANCED_MODEL` config (default anthropic/claude-opus-4-7,
  the latest Opus).
- Drop `CHAT_FAST_MODEL` — "fast" is the *path* (baseline vs SDK),
  not a model tier.
- New `resolve_chat_model(tier)` in service.py; both paths use it.
- SDK `_resolve_model_and_multiplier` reads `config.advanced_model`
  instead of the hardcoded opus string.
- Baseline `_resolve_baseline_model` now takes `CopilotLlmModel`
  (tier) instead of `CopilotMode` and threads `model` through
  `stream_chat_completion_baseline`.

So users can now pick Fast + Advanced (baseline path, Opus) or any
other combination, and bumping the advanced default is an env-var
change instead of a code change.
Comment thread autogpt_platform/backend/backend/copilot/sdk/service.py
sentry[bot] caught that ``_compress_messages`` didn't filter
role="reasoning" before serialising messages to the LLM compressor.
``compress_context`` doesn't understand the reasoning role — it
would either silently drop the row or pass malformed JSON to the
compression LLM.  ``_build_query_message`` already filtered
upstream, but ``_seed_transcript`` (and any future caller) went
through unfiltered.

Centralise the filter in ``_compress_messages`` so every caller is
covered.
Comment thread autogpt_platform/backend/backend/copilot/sdk/response_adapter.py
…uard

sentry[bot] caught that the thinking-only-final-turn fallback emitted
``StreamTextStart`` + ``StreamTextDelta`` without first closing any
reasoning block that was still open.  AI SDK v5 maps distinct
start/end event pairs to distinct UI parts — interleaving a text
delta inside an open reasoning block breaks the wire contract and
can corrupt the frontend render.

Call ``_end_reasoning_if_open`` before ``_ensure_text_started`` in
the guard so text and reasoning blocks stay strictly sequential.
@majdyz
majdyz merged commit 70b591d into dev Apr 19, 2026
44 checks passed
@majdyz
majdyz deleted the fix/copilot-queued-message-streaming branch April 19, 2026 03:37
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to ✅ Done in AutoGPT development kanban Apr 19, 2026
@github-project-automation github-project-automation Bot moved this to Done in Frontend Apr 19, 2026
majdyz added a commit that referenced this pull request Apr 21, 2026
…nRouter (#12870)

### Why / What / How

**Why:** Fast-mode autopilot never renders a Reasoning block. The
frontend already has `ReasoningCollapse` wired up and the wire protocol
already carries `StreamReasoning*` events (landed for SDK mode in
#12853), but the baseline (OpenRouter OpenAI-compat) path never asks
Anthropic for extended thinking and never parses reasoning deltas off
the stream. Result: users on fast/standard get a good answer with no
visible chain-of-thought, while SDK users see the full Reasoning
collapse.

**What:** Plumb reasoning end-to-end through the baseline path by opting
into OpenRouter's non-OpenAI `reasoning` extension, parsing the
reasoning delta fields off each chunk, and emitting the same
`StreamReasoningStart/Delta/End` events the SDK adapter already uses.

**How:**
- **New config:** `baseline_reasoning_max_tokens` (default 8192; 0
disables). Sent as `extra_body={"reasoning": {"max_tokens": N}}` only on
Anthropic routes — other providers drop the field, and
`is_anthropic_model()` already gates this.
- **Delta extraction:** `_extract_reasoning_delta()` handles all three
OpenRouter/provider variants in priority order — legacy
`delta.reasoning` (string), DeepSeek-style `delta.reasoning_content`,
and the structured `delta.reasoning_details` list (text/summary entries;
encrypted or unknown entries are skipped).
- **Event emission:** Reasoning uses the same state-machine rules the
SDK adapter uses — a text delta or tool_use delta arriving mid-stream
closes the open reasoning block first, so the AI SDK v5 transport keeps
reasoning / text / tool-use as distinct UI parts. On stream end, any
still-open reasoning block gets a matching `reasoning-end` so a
reasoning-only turn still finalises the frontend collapse.
- **Scope:** Live streaming only. Reasoning is not persisted to
`ChatMessage` rows or the transcript builder in this PR (SDK path does
so via `content_blocks=[{type: 'thinking', ...}]`, but that round-trip
requires Anthropic signature plumbing baseline doesn't have today).
Reload will still not show reasoning on baseline sessions — can follow
up if we decide it's worth the signature handling.

### Changes

- `backend/copilot/config.py` — new `baseline_reasoning_max_tokens`
field.
- `backend/copilot/baseline/service.py` — new
`_extract_reasoning_delta()` helper; reasoning block state on
`_BaselineStreamState`; `reasoning` gated into `extra_body`; chunk loop
emits `StreamReasoning*` events with text/tool_use transition rules;
stream-end closes any open reasoning block.
- `backend/copilot/baseline/service_unit_test.py` — 11 new tests
covering extractor variants (legacy string, deepseek alias, structured
list with text/summary aliases, encrypted-skip, empty), paired event
ordering (reasoning-end before text-start), reasoning-only streams, and
that the `reasoning` request param is correctly gated by model route
(Anthropic vs non-Anthropic) and by the config flag.

### Checklist

For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [ ] I have tested my changes according to the test plan:
- [x] `poetry run pytest backend/copilot/baseline/service_unit_test.py
backend/copilot/baseline/transcript_integration_test.py` — 103 passed
- [ ] Manual: with `CHAT_USE_CLAUDE_AGENT_SDK=false` and
`CHAT_MODEL=anthropic/claude-sonnet-4-6`, send a multi-step prompt on
fast mode and confirm a Reasoning collapse appears alongside the final
text
- [ ] Manual: flip `CHAT_BASELINE_REASONING_MAX_TOKENS=0` and confirm
baseline responses revert to text-only (no reasoning param, no reasoning
UI)
- [ ] Manual: with a non-Anthropic baseline model (`openai/gpt-4o`),
confirm the request does NOT include `reasoning` and nothing regresses

For configuration changes:
- [x] `.env.default` is compatible — new setting falls back to the
pydantic default
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/xl

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant