Skip to content

fix(backend): Auto-fork marketplace agent on first save - #11999

Closed
Swiftyos wants to merge 2 commits into
devfrom
fix/fork-marketplace-agent-on-save
Closed

fix(backend): Auto-fork marketplace agent on first save#11999
Swiftyos wants to merge 2 commits into
devfrom
fix/fork-marketplace-agent-on-save

Conversation

@Swiftyos

@Swiftyos Swiftyos commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

When a user adds a marketplace agent to their library and clicks "Edit Agent" → makes changes → clicks "Save", they get Error saving agent ApiError: Graph #... not found.

Root cause: The update_graph endpoint (PUT /graphs/{graph_id}) calls get_graph_all_versions(graph_id, user_id) which filters by userId. Marketplace agents are owned by the original creator, not the current user, so this returns empty → 404.

Changes 🏗️

  • Modified update_graph() in v1.py to auto-fork when the user doesn't own the graph but has it in their library
  • When existing_versions is empty, checks for a LibraryAgent entry for the user
  • If found, creates a user-owned fork with the submitted edits applied (new graph ID, new node IDs, forked_from_id/forked_from_version set for provenance)
  • Activates the forked graph and creates a new LibraryAgent entry for it
  • Returns the new graph — the frontend already handles ID changes from the save response

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • Add a marketplace agent to library
    • Click Edit Agent → opens builder
    • Make a change → Save
    • Verify: no error, URL updates to new graph ID, agent saves successfully
    • Verify: original marketplace agent still in library alongside the new fork
    • Verify: saving the forked agent again works normally (owned by user now)

@Swiftyos
Swiftyos requested a review from a team as a code owner February 6, 2026 16:02
@Swiftyos
Swiftyos requested review from Bentlybro and Otto-AGPT and removed request for a team February 6, 2026 16:02
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Feb 6, 2026
@github-actions github-actions Bot added platform/backend AutoGPT Platform - Back end size/m labels Feb 6, 2026
@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

update_graph now forks a marketplace-owned graph into a user-owned copy (if the user has no existing versions), activates the fork, creates a corresponding library agent, and proceeds to apply edits on the forked graph. Separate typing changes adjust helper return types for LLM tool formatting and parallel tool calls.

Changes

Cohort / File(s) Summary
Graph forking & library agent flow
autogpt_platform/backend/backend/api/features/v1.py
When no user-owned versions exist, update_graph locates a marketplace graph in the user's library, forks it into a user-owned graph, activates the fork, creates the library agent record for the fork, and continues with the normal update flow using the fork.
LLM tool typing adjustments
autogpt_platform/backend/backend/blocks/llm.py
Return type annotations changed: convert_openai_tool_fmt_to_anthropic now may return anthropic.NotGiven, and get_parallel_tool_calls_param may return openai.NotGiven. Call sites updated to use NOT_GIVEN constants and include # type: ignore where needed.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant API as Backend API (update_graph)
participant Library as User Library/DB
participant Marketplace as Marketplace Graphs/DB
participant AgentSvc as Library Agent Service

Client->>API: Request update on graph (no user versions)
API->>Library: Check for existing user graph versions
Library-->>API: None found
API->>Library: Find marketplace-origin graph in user's library
Library-->>Marketplace: Retrieve marketplace graph (source)
Marketplace-->>Library: Return marketplace graph data
API->>Library: Fork marketplace graph -> create user-owned graph
Library-->>API: Return forked graph id
API->>Library: Activate forked graph as user's active version
API->>AgentSvc: Create corresponding library agent for forked graph
AgentSvc-->>API: Agent created
API->>Client: Proceed with applying edits to forked graph (standard update flow)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 3/5

Suggested reviewers

  • ntindle
  • Bentlybro

Poem

🐰 I hopped through graphs both big and small,
Forked a marketplace to make it all mine,
Activated, created an agent for the call,
Now edits apply where the stars align. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% 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 'fix(backend): Auto-fork marketplace agent on first save' accurately describes the main change: auto-forking marketplace agents when users save edits.
Description check ✅ Passed The description clearly explains the root cause, detailed changes made, and provides a comprehensive test plan directly related to the changeset.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/fork-marketplace-agent-on-save

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
autogpt_platform/backend/backend/api/features/v1.py (3)

830-851: Consider wrapping the fork block in error handling.

If any of the fork-related operations fail (e.g., fork_graph raises ValueError, on_graph_activate fails, set_graph_active_version fails), the user gets an opaque 500 error. A try/except converting known failures to appropriate HTTP status codes would improve the developer and user experience.

Proposed error handling
         if not library_agent:
             raise HTTPException(404, detail=f"Graph #{graph_id} not found")
 
         # Fork the marketplace agent to create a user-owned copy
-        forked = await graph_db.fork_graph(
-            graph_id, library_agent.graph_version, user_id
-        )
+        try:
+            forked = await graph_db.fork_graph(
+                graph_id, library_agent.graph_version, user_id
+            )
+        except ValueError:
+            raise HTTPException(
+                404, detail=f"Graph #{graph_id} could not be forked"
+            )
         forked = await on_graph_activate(forked, user_id=user_id)

846-846: create_library_agent for the fork may create a duplicate library entry with isCreatedByUser=True always.

Looking at the create_library_agent snippet, the isCreatedByUser field is set as (user_id == user_id) which is always True. This means the forked library entry will be marked as user-created, which is technically correct for a fork. However, the user will now have two library entries — the original marketplace agent and the new fork. Per PR objectives this is intentional, but consider whether the old marketplace library entry should be soft-deleted or annotated to avoid confusion.


848-851: The fork creates an intermediate unused graph version.

fork_graph creates version N of the forked graph, then the normal update path (Line 853 onward) immediately creates version N+1 with the user's actual edits. Version N is never the final active version — it's activated at Line 842-844, then deactivated at Line 870-871 when N+1 becomes active. This leaves an orphan version in the database.

This is functionally correct but slightly wasteful. A potential optimization would be to apply the user's edits directly to the forked graph before persisting, but that would be a larger refactor.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 68f419d and 46c65cb.

📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/blocks/llm.py
✅ Files skipped from review due to trivial changes (1)
  • autogpt_platform/backend/backend/blocks/llm.py
🧰 Additional context used
📓 Path-based instructions (6)
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

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
autogpt_platform/backend/backend/api/features/**/*.py

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

Update routes in '/backend/backend/api/features/' and add/update Pydantic models in the same directory for API development

When modifying API routes, update corresponding Pydantic models in the same directory and write tests alongside the route file

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
autogpt_platform/backend/**/*.{py,txt}

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

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
autogpt_platform/backend/backend/api/**/*.py

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

autogpt_platform/backend/backend/api/**/*.py: Use FastAPI for building REST and WebSocket endpoints
Use JWT-based authentication with Supabase integration

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
autogpt_platform/backend/backend/**/*.py

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

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/api/features/v1.py
🧬 Code graph analysis (1)
autogpt_platform/backend/backend/api/features/v1.py (3)
autogpt_platform/backend/backend/api/features/library/db.py (2)
  • get_library_agent_by_graph_id (346-373)
  • create_library_agent (409-478)
autogpt_platform/backend/backend/data/graph.py (2)
  • fork_graph (1436-1454)
  • set_graph_active_version (1261-1283)
autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py (1)
  • on_graph_activate (21-32)
⏰ 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: Seer Code Review
  • GitHub Check: types
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: Analyze (python)
  • GitHub Check: Check PR Status
🔇 Additional comments (1)
autogpt_platform/backend/backend/api/features/v1.py (1)

838-841: Add error handling for fork_graph ValueError.

fork_graph can raise ValueError if the graph is not found (line 1442 in graph.py), but the call at line 839-841 has no try/except. If this occurs, the error will surface as an unhandled 500 instead of a proper HTTP error. Consider wrapping the fork_graph call in a try/except to catch ValueError and return an appropriate HTTP error response (e.g., 404).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@Otto-AGPT
Otto-AGPT requested review from ntindle and removed request for Otto-AGPT February 6, 2026 17:20

@ntindle ntindle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is this not using fork_graph from backend/data/graph.py:1347?

@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to 🚧 Needs work in AutoGPT development kanban Feb 9, 2026

@ntindle ntindle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code Review

The fork logic works correctly, but there's an existing fork_graph() function in backend/data/graph.py:1347 that handles the same operation:

async def fork_graph(graph_id: str, graph_version: int, user_id: str) -> GraphModel:
    graph = await get_graph(graph_id, graph_version, user_id=user_id, for_export=True)
    graph.forked_from_id = graph.id
    graph.forked_from_version = graph.version
    graph.name = f"{graph.name} (copy)"
    graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
    graph.validate_graph(for_run=False)
    # ... creates graph in transaction

Suggestion: Refactor to use the existing fork_graph() function, then apply the user's edits to the forked graph. This avoids code duplication and ensures consistent fork behavior across the codebase.

The current inline implementation works, but using the shared function would be cleaner and more maintainable.

@github-actions github-actions Bot added platform/blocks conflicts Automatically applied to PRs with merge conflicts labels Feb 9, 2026
@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request.

@Swiftyos
Swiftyos requested a review from ntindle February 9, 2026 11:30
@github-actions github-actions Bot added the size/l label Feb 9, 2026
… not found" error

When a user adds a marketplace agent to their library and tries to save
edits, the update_graph endpoint returned 404 because the graph is owned
by the original creator. Now, if the user has the graph in their library
but doesn't own it, a fork is automatically created with their edits
applied, new IDs assigned, and a new library agent entry created.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/blocks/exa/websets_monitor.py (1)

263-269: ⚠️ Potential issue | 🟠 Major

Test mock for create is not awaitable — will raise TypeError at test time.

The run() method now does await aexa.websets.monitors.create(params=payload) (line 323), but the test mock uses a plain lambda which returns a non-awaitable MagicMock. This will fail with TypeError: object MagicMock can't be used in 'await' expression.

Use AsyncMock as done in websets_import_export.py (line 250).

Proposed fix
-        from unittest.mock import MagicMock
+        from unittest.mock import AsyncMock, MagicMock
 
         ...
 
         return {
             "_get_client": lambda *args, **kwargs: MagicMock(
                 websets=MagicMock(
-                    monitors=MagicMock(create=lambda *args, **kwargs: mock_monitor)
+                    monitors=MagicMock(create=AsyncMock(return_value=mock_monitor))
                 )
             )
         }
🤖 Fix all issues with AI agents
In `@autogpt_platform/backend/backend/blocks/llm.py`:
- Around line 532-539: The function convert_openai_tool_fmt_to_anthropic (and
any other places using anthropic.Omit() or openai.Omit() for body/request
fields) should return the SDK body-sentinel NOT_GIVEN instead of Omit; replace
anthropic.Omit() with anthropic.NOT_GIVEN (and openai.Omit() with
openai.NOT_GIVEN where used for body params like tools in
client.messages.create() and parallel_tool_calls in
client.chat.completions.create()) to match the existing tools_param pattern that
uses openai.NOT_GIVEN and ensure correct SDK semantics.
🧹 Nitpick comments (1)
autogpt_platform/backend/backend/blocks/exa/websets.py (1)

254-396: ExaCreateWebsetBlock still uses the synchronous Exa client.

While this PR correctly converts other blocks to AsyncExa + await, ExaCreateWebsetBlock.run() (line 258) still instantiates the synchronous Exa client and makes blocking calls (exa.websets.create, exa.websets.wait_until_idle). These blocking calls will tie up the event loop in an async context. Consider converting this block to AsyncExa as well for consistency.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eabc7bb and 4cb6d15.

📒 Files selected for processing (9)
  • autogpt_platform/backend/backend/api/features/v1.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/llm.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • autogpt_platform/backend/backend/api/features/v1.py
🧰 Additional context used
📓 Path-based instructions (6)
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

Files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/backend/backend/blocks/**/*.py

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

autogpt_platform/backend/backend/blocks/**/*.py: Inherit from 'Block' base class with input/output schemas when adding new blocks in backend
Implement 'run' method with proper error handling in backend blocks
Generate block UUID using 'uuid.uuid4()' when creating new blocks in backend
Write tests alongside block implementation when adding new blocks in backend

Backend architecture uses Blocks in backend/backend/blocks/ as reusable components that perform specific tasks

Files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/backend/**/*.{py,txt}

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

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/backend/backend/**/*.py

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

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/backend/backend/blocks/*.py

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

autogpt_platform/backend/backend/blocks/*.py: When creating new blocks, inherit from the Block base class and define input/output schemas using BlockSchema
Implement blocks with an async run method and generate unique block IDs using uuid.uuid4()
When working with files in blocks, use store_media_file() from backend.util.file with appropriate return_format parameter: for_local_processing for local tools, for_external_api for external APIs, for_block_output for block outputs
Always use for_block_output format in store_media_file() for block outputs unless there is a specific reason not to
Never hardcode workspace checks when using store_media_file() - let for_block_output handle context adaptation automatically
When adding new blocks, analyze block interfaces to ensure inputs and outputs tie well together for productive graph-based editor connections

Files:

  • autogpt_platform/backend/backend/blocks/llm.py
🧠 Learnings (2)
📚 Learning: 2026-02-05T04:11:00.596Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/concat.py:3-4
Timestamp: 2026-02-05T04:11:00.596Z
Learning: In autogpt_platform/backend/backend/blocks/**/*.py, when creating a new block, generate a UUID once with uuid.uuid4() and hard-code the resulting string as the block's id parameter. Do not call uuid.uuid4() at runtime; IDs must be constant across all imports and runs to ensure stability.

Applied to files:

  • autogpt_platform/backend/backend/blocks/exa/websets_search.py
  • autogpt_platform/backend/backend/blocks/exa/websets.py
  • autogpt_platform/backend/backend/blocks/exa/websets_items.py
  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_polling.py
  • autogpt_platform/backend/backend/blocks/llm.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
  • autogpt_platform/backend/backend/blocks/exa/websets_import_export.py
📚 Learning: 2025-11-03T17:41:48.242Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11185
File: autogpt_platform/backend/backend/blocks/exa/webhook_blocks.py:133-161
Timestamp: 2025-11-03T17:41:48.242Z
Learning: The ExaWebsetWebhookBlock in autogpt_platform/backend/backend/blocks/exa/webhook_blocks.py is currently disabled (disabled=True in __init__) and not active in production.

Applied to files:

  • autogpt_platform/backend/backend/blocks/exa/websets_monitor.py
  • autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py
🧬 Code graph analysis (2)
autogpt_platform/backend/backend/blocks/exa/websets.py (1)
autogpt_platform/backend/backend/integrations/creds_manager.py (1)
  • update (162-164)
autogpt_platform/backend/backend/blocks/exa/websets_monitor.py (1)
autogpt_platform/backend/backend/integrations/creds_manager.py (1)
  • update (162-164)
🔇 Additional comments (21)
autogpt_platform/backend/backend/blocks/exa/websets.py (10)

481-503: Async conversion looks correct.

The ExaCreateOrFindWebsetBlock properly awaits both aexa.websets.get and aexa.websets.create calls.


557-557: LGTMExaUpdateWebsetBlock correctly awaits the async SDK call.


620-623: LGTMExaListWebsetsBlock correctly awaits the async list call.


681-681: LGTMExaGetWebsetBlock correctly awaits the async get call.


751-751: LGTMExaDeleteWebsetBlock correctly awaits the async delete call.


801-801: LGTMExaCancelWebsetBlock correctly awaits the async cancel call.


971-971: LGTMExaPreviewWebsetBlock correctly awaits the async preview call.


1054-1054: LGTMExaWebsetStatusBlock correctly awaits the async get call.


1188-1216: LGTMExaWebsetSummaryBlock correctly awaits both websets.get and websets.items.list.


1365-1365: LGTMExaWebsetReadyCheckBlock correctly awaits the async get call.

autogpt_platform/backend/backend/blocks/exa/websets_polling.py (3)

124-128: LGTM — All polling blocks correctly converted to async SDK calls with proper await.


348-350: LGTMExaWaitForSearchBlock correctly awaits search status polling and final lookup.

Also applies to: 404-406


506-508: LGTMExaWaitForEnrichmentBlock correctly awaits enrichment polling, final lookup, and item listing in _get_sample_enrichments.

Also applies to: 551-553, 578-578

autogpt_platform/backend/backend/blocks/exa/websets_items.py (2)

181-183: LGTM — All item management blocks correctly converted to async SDK calls.


411-411: LGTMExaBulkWebsetItemsBlock correctly uses async for to iterate the async item stream.

autogpt_platform/backend/backend/blocks/exa/websets_enrichment.py (1)

205-207: LGTM — All enrichment blocks correctly converted to async SDK calls, including the polling loop and cancel flow.

autogpt_platform/backend/backend/blocks/exa/websets_monitor.py (1)

323-323: LGTM — All monitor blocks correctly converted to async SDK calls.

autogpt_platform/backend/backend/blocks/exa/websets_import_export.py (2)

225-253: LGTM — Test mock correctly uses AsyncMock for the awaited create call and an async generator for list_all.


578-589: LGTM — Async iterator mock and async for usage are correct.

autogpt_platform/backend/backend/blocks/exa/websets_search.py (1)

320-322: LGTM — All search blocks correctly converted to async SDK calls, including polling and find-or-create flows.

autogpt_platform/backend/backend/blocks/llm.py (1)

597-603: Use the omit singleton instead of openai.Omit() to follow SDK best practices.

The code creates a new instance with openai.Omit(), but the openai SDK v1.97.1 documentation recommends using the omit singleton. Replace:

def get_parallel_tool_calls_param(
    llm_model: LlmModel, parallel_tool_calls: bool | None
) -> bool | openai.Omit:
    """Get the appropriate parallel_tool_calls parameter for OpenAI-compatible APIs."""
    if llm_model.startswith("o") or parallel_tool_calls is None:
        return openai.Omit()
    return parallel_tool_calls

with:

from openai import omit

def get_parallel_tool_calls_param(
    llm_model: LlmModel, parallel_tool_calls: bool | None
) -> bool | omit.__class__:  # or use Literal type hint
    """Get the appropriate parallel_tool_calls parameter for OpenAI-compatible APIs."""
    if llm_model.startswith("o") or parallel_tool_calls is None:
        return omit
    return parallel_tool_calls

Additionally, this creates inconsistency with other usage of openai.NOT_GIVEN elsewhere in the file (lines 662, 822, 864, 934). Consider standardizing on a single sentinel approach.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread autogpt_platform/backend/backend/blocks/llm.py Outdated
@Swiftyos
Swiftyos force-pushed the fix/fork-marketplace-agent-on-save branch from 4cb6d15 to 68f419d Compare February 9, 2026 13:36
@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly.

@github-actions github-actions Bot removed conflicts Automatically applied to PRs with merge conflicts platform/blocks labels Feb 9, 2026
Comment on lines +841 to +851
)
forked = await on_graph_activate(forked, user_id=user_id)
await graph_db.set_graph_active_version(
graph_id=forked.id, version=forked.version, user_id=user_id
)
await library_db.create_library_agent(forked, user_id)

# Apply the user's edits on top of the fork via the normal update path
graph_id = forked.id
graph.id = forked.id
existing_versions = [forked]

This comment was marked as outdated.

@Swiftyos
Swiftyos force-pushed the fix/fork-marketplace-agent-on-save branch from 68f419d to 46c65cb Compare February 9, 2026 13:46
Comment on lines +839 to +847
forked = await graph_db.fork_graph(
graph_id, library_agent.graph_version, user_id
)
forked = await on_graph_activate(forked, user_id=user_id)
await graph_db.set_graph_active_version(
graph_id=forked.id, version=forked.version, user_id=user_id
)
await library_db.create_library_agent(forked, user_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The agent forking process is not atomic. A failure mid-sequence can create orphaned graphs that are persisted to the database but are in an incomplete state.
Severity: MEDIUM

Suggested Fix

Wrap the entire forking sequence, from fork_graph() through create_library_agent(), in a single, all-or-nothing database transaction. This ensures that if any step fails, all preceding database changes are rolled back, preventing the creation of orphaned graph records.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: autogpt_platform/backend/backend/api/features/v1.py#L839-L847

Potential issue: When forking a marketplace agent, the sequence of
operations—`fork_graph()`, `on_graph_activate()`, `set_graph_active_version()`, and
`create_library_agent()`—is not wrapped in a single atomic transaction. If a failure
occurs after `fork_graph()` succeeds but before the subsequent steps complete (e.g., due
to a network timeout or service error), the newly created forked graph will be left in
an incomplete state. This results in an 'orphaned' graph that exists in the database but
is not properly activated or linked in the user's library, leading to data
inconsistency.

@ntindle
ntindle enabled auto-merge February 11, 2026 22:26

@autogpt-reviewer autogpt-reviewer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📋 PR #11999 — fix(backend): Auto-fork marketplace agent on first save

Author: Swiftyos | Reviewer: ntindle (approved) | Files: v1.py (+22/-1), llm.py (+9/-9)


🎯 Verdict: APPROVE

This is a well-scoped bug fix that resolves a real user-facing error ("Graph not found" when saving edits to a marketplace agent). The implementation correctly reuses existing fork_graph() infrastructure, has zero impact on the normal save path, and has already been approved by maintainer ntindle. All CI checks pass.


What This PR Does

When a user adds a marketplace agent to their library and tries to save edits, update_graph() failed because it filters by userId — but marketplace agents are owned by the original creator. This fix auto-detects the situation, forks the marketplace agent into a user-owned copy, then applies the edits normally. The frontend already handles graph ID changes from save responses, so this works transparently.

Secondary change: llm.py switches from deprecated Omit/omit sentinels to NotGiven/NOT_GIVEN for OpenAI and Anthropic SDK parameters.


Specialist Findings

🛡️ Security ✅ — No vulnerabilities found. Fork path is properly gated by two auth checks: (1) user doesn't own graph, (2) user has a LibraryAgent entry. No IDOR vectors — user_id comes from JWT, forked graph gets new UUID, original graph is untouched.

🏗️ Architecture ✅ — Sound placement. Auto-fork inside update_graph() is correct — transparent to frontend, no API changes needed. Clean reuse of existing functions (fork_graph, on_graph_activate, set_graph_active_version, create_library_agent). Minor: fork-then-update creates one orphan intermediate version, but this is a one-time cost per marketplace agent and acceptable.

Performance ✅ — Zero impact on normal path. Fork path adds ~5 sequential DB operations but only fires once per marketplace agent per user. Estimated 50-200ms added latency on a save operation — acceptable. No N+1 patterns.

🧪 Testing ⚠️ — No tests for the new fork path. The PR adds 22 lines of new branching logic with zero automated tests. Existing CI is green (no regressions), and the change uses well-tested functions, so risk is low. However, a happy-path test for the auto-fork scenario should be added as a follow-up.

📖 Quality ✅ — Clean, readable code with good inline comments explaining the fork logic. Follows existing patterns. Minor: function docstring doesn't mention new auto-fork behavior; # type: ignore comments could be more specific.

📦 Product ✅ — Fixes a real user-facing bug with an intuitive solution. Silent fork is frictionless — save just works. The "(copy)" suffix provides indication of the fork. User ends up with both original marketplace agent and fork in library, which is intentional and documented.

📬 Discussion ✅ — All reviewer concerns addressed. ntindle's request to use existing fork_graph() was implemented and approved. CodeRabbit's error handling suggestion remains open but non-blocking.

🔎 QA ✅ — Environment healthy (frontend, backend, marketplace all working). No UI regressions observed. Could not fully exercise the fork path in live testing due to needing multi-user seeded data, but the normal save flow works correctly.


Should Fix (Follow-up OK)

  1. v1.py:839-841 — Wrap fork_graph() call in try/except to catch ValueError and return HTTP 404 instead of unhandled 500. Edge case (library entry exists but graph doesn't = data inconsistency), but would improve error handling.

  2. Test coverage — Add at least a happy-path integration test for the auto-fork flow: user saves marketplace agent → fork created → edits applied → subsequent saves work normally.

  3. Docstring — Update update_graph() docstring to document the auto-fork behavior for marketplace agents.


Risk Assessment

Merge risk: LOW — Additive change in a single code path, doesn't modify existing update behavior, uses well-tested existing functions, all CI green, maintainer-approved.

Rollback: EASY — Reverting restores the original 404 behavior. No migrations, no schema changes.


Automated review by Review Squad — 8 specialists spawned, 8 reported.

@Bentlybro
Bentlybro removed their request for review March 2, 2026 12:08
@Swiftyos Swiftyos closed this Mar 4, 2026
auto-merge was automatically disabled March 4, 2026 10:54

Pull request was closed

@github-project-automation github-project-automation Bot moved this from 👍🏼 Mergeable to ✅ Done in AutoGPT development kanban Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

3 participants