fix(backend): Auto-fork marketplace agent on first save - #11999
Conversation
Walkthroughupdate_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
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🧰 Additional context used📓 Path-based instructions (6)autogpt_platform/backend/**/*.py📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
autogpt_platform/backend/backend/api/features/**/*.py📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
autogpt_platform/backend/**/*.{py,txt}📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Files:
autogpt_platform/backend/backend/api/**/*.py📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Files:
autogpt_platform/backend/backend/**/*.py📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Files:
autogpt_platform/**/*.py📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧬 Code graph analysis (1)autogpt_platform/backend/backend/api/features/v1.py (3)
⏰ 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)
🔇 Additional comments (1)
✏️ Tip: You can disable this entire section by setting 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. Comment |
ntindle
left a comment
There was a problem hiding this comment.
why is this not using fork_graph from backend/data/graph.py:1347?
ntindle
left a comment
There was a problem hiding this comment.
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 transactionSuggestion: 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.
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
… 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.
There was a problem hiding this comment.
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 | 🟠 MajorTest mock for
createis not awaitable — will raiseTypeErrorat test time.The
run()method now doesawait aexa.websets.monitors.create(params=payload)(line 323), but the test mock uses a plainlambdawhich returns a non-awaitableMagicMock. This will fail withTypeError: object MagicMock can't be used in 'await' expression.Use
AsyncMockas done inwebsets_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:ExaCreateWebsetBlockstill uses the synchronousExaclient.While this PR correctly converts other blocks to
AsyncExa+await,ExaCreateWebsetBlock.run()(line 258) still instantiates the synchronousExaclient 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 toAsyncExaas 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.
📒 Files selected for processing (9)
autogpt_platform/backend/backend/api/features/v1.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_platform/backend/backend/blocks/exa/websets_import_export.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/exa/websets_search.pyautogpt_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.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_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 backendBackend architecture uses Blocks in
backend/backend/blocks/as reusable components that perform specific tasks
Files:
autogpt_platform/backend/backend/blocks/exa/websets_search.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_platform/backend/backend/blocks/exa/websets_import_export.py
autogpt_platform/backend/**/*.{py,txt}
📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)
Use
poetry runprefix for all Python commands, including testing, linting, formatting, and migrations
Files:
autogpt_platform/backend/backend/blocks/exa/websets_search.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_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.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_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.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_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 theBlockbase class and define input/output schemas usingBlockSchema
Implement blocks with an asyncrunmethod and generate unique block IDs usinguuid.uuid4()
When working with files in blocks, usestore_media_file()frombackend.util.filewith appropriatereturn_formatparameter:for_local_processingfor local tools,for_external_apifor external APIs,for_block_outputfor block outputs
Always usefor_block_outputformat instore_media_file()for block outputs unless there is a specific reason not to
Never hardcode workspace checks when usingstore_media_file()- letfor_block_outputhandle 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.pyautogpt_platform/backend/backend/blocks/exa/websets.pyautogpt_platform/backend/backend/blocks/exa/websets_items.pyautogpt_platform/backend/backend/blocks/exa/websets_monitor.pyautogpt_platform/backend/backend/blocks/exa/websets_polling.pyautogpt_platform/backend/backend/blocks/llm.pyautogpt_platform/backend/backend/blocks/exa/websets_enrichment.pyautogpt_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.pyautogpt_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
ExaCreateOrFindWebsetBlockproperly awaits bothaexa.websets.getandaexa.websets.createcalls.
557-557: LGTM —ExaUpdateWebsetBlockcorrectly awaits the async SDK call.
620-623: LGTM —ExaListWebsetsBlockcorrectly awaits the async list call.
681-681: LGTM —ExaGetWebsetBlockcorrectly awaits the async get call.
751-751: LGTM —ExaDeleteWebsetBlockcorrectly awaits the async delete call.
801-801: LGTM —ExaCancelWebsetBlockcorrectly awaits the async cancel call.
971-971: LGTM —ExaPreviewWebsetBlockcorrectly awaits the async preview call.
1054-1054: LGTM —ExaWebsetStatusBlockcorrectly awaits the async get call.
1188-1216: LGTM —ExaWebsetSummaryBlockcorrectly awaits bothwebsets.getandwebsets.items.list.
1365-1365: LGTM —ExaWebsetReadyCheckBlockcorrectly 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 properawait.
348-350: LGTM —ExaWaitForSearchBlockcorrectly awaits search status polling and final lookup.Also applies to: 404-406
506-508: LGTM —ExaWaitForEnrichmentBlockcorrectly 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: LGTM —ExaBulkWebsetItemsBlockcorrectly usesasync forto 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 usesAsyncMockfor the awaitedcreatecall and an async generator forlist_all.
578-589: LGTM — Async iterator mock andasync forusage 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 theomitsingleton instead ofopenai.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 theomitsingleton. 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_callswith:
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_callsAdditionally, this creates inconsistency with other usage of
openai.NOT_GIVENelsewhere 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.
4cb6d15 to
68f419d
Compare
|
Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly. |
| ) | ||
| 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.
This comment was marked as outdated.
Sorry, something went wrong.
68f419d to
46c65cb
Compare
| 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) | ||
|
|
There was a problem hiding this comment.
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.
autogpt-reviewer
left a comment
There was a problem hiding this comment.
📋 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
📖 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)
-
v1.py:839-841— Wrapfork_graph()call in try/except to catchValueErrorand return HTTP 404 instead of unhandled 500. Edge case (library entry exists but graph doesn't = data inconsistency), but would improve error handling. -
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.
-
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.
Pull request was closed
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_graphendpoint (PUT /graphs/{graph_id}) callsget_graph_all_versions(graph_id, user_id)which filters byuserId. Marketplace agents are owned by the original creator, not the current user, so this returns empty → 404.Changes 🏗️
update_graph()inv1.pyto auto-fork when the user doesn't own the graph but has it in their libraryexisting_versionsis empty, checks for aLibraryAgententry for the userforked_from_id/forked_from_versionset for provenance)LibraryAgententry for itChecklist 📋
For code changes: