diff --git a/multi-agent/api/flask/endpoints/blueprints.py b/multi-agent/api/flask/endpoints/blueprints.py
index 027e5007b..cec42f02c 100644
--- a/multi-agent/api/flask/endpoints/blueprints.py
+++ b/multi-agent/api/flask/endpoints/blueprints.py
@@ -174,6 +174,39 @@ def save_blueprint(blueprint_raw=None, user_id="alice", metadata=None):
return jsonify({"status": "error", "error": str(e)}), 500
+@blueprints_bp.route("/blueprint.update", methods=["PUT"])
+@from_body({
+ "blueprint_id": fields.Str(data_key="blueprintId", required=True),
+ "blueprint_raw": fields.Str(data_key="blueprintRaw", required=True),
+})
+def update_blueprint(blueprint_id, blueprint_raw):
+ """Update an existing blueprint in-place, keeping the same ID."""
+ try:
+ parsed = _extract_blueprint_data(
+ json_field_value=blueprint_raw,
+ field_name="blueprint_raw"
+ )
+
+ svc = current_app.container.blueprint_service
+ success = svc.update_draft(blueprint_id=blueprint_id, draft_dict=parsed)
+
+ if not success:
+ return jsonify({"status": "error", "error": "Failed to update blueprint"}), 500
+
+ return jsonify({
+ "status": "success",
+ "blueprint_id": blueprint_id,
+ }), 200
+
+ except BlueprintNotFoundError as e:
+ return jsonify({"status": "error", "error": str(e)}), 404
+ except BadRequest as e:
+ return jsonify({"status": "error", "error": str(e)}), 400
+ except Exception as e:
+ logger.exception(f"Unexpected error updating blueprint {blueprint_id}")
+ return jsonify({"status": "error", "error": str(e)}), 500
+
+
@blueprints_bp.route("/blueprint.info.get", methods=["GET"])
@from_query({
"blueprint_id": fields.Str(data_key="blueprintId", required=True)
diff --git a/multi-agent/blueprints/service.py b/multi-agent/blueprints/service.py
index eabe52995..6fb0e3ea6 100644
--- a/multi-agent/blueprints/service.py
+++ b/multi-agent/blueprints/service.py
@@ -42,12 +42,15 @@ def get_blueprint_draft_doc(self, blueprint_id: str) -> BlueprintDocument:
"""Get blueprint document with metadata for sharing operations."""
return self._repo.load(blueprint_id)
- def update_draft(self, *, blueprint_id: str, draft_dict: dict) -> bool: # NEW
+ def update_draft(self, *, blueprint_id: str, draft_dict: dict) -> bool:
draft = BlueprintDraft(**draft_dict)
rid_refs = list(RefWalker.external_rids(draft))
- return self._repo.update(
- blueprint_id=blueprint_id, spec=draft, rid_refs=rid_refs
- )
+ try:
+ return self._repo.update(
+ blueprint_id=blueprint_id, spec=draft, rid_refs=rid_refs
+ )
+ except KeyError:
+ raise BlueprintNotFoundError(blueprint_id) from None
def load_resolved(self, blueprint_id: str) -> BlueprintSpec:
return self._resolver.resolve(self.load_draft(blueprint_id))
diff --git a/ui/client/src/api/blueprints.ts b/ui/client/src/api/blueprints.ts
index f23d710bf..5a712bb97 100644
--- a/ui/client/src/api/blueprints.ts
+++ b/ui/client/src/api/blueprints.ts
@@ -126,6 +126,20 @@ export async function saveBlueprint(
return data;
}
+/**
+ * Update an existing blueprint in-place (keeps the same ID)
+ */
+export async function updateBlueprint(
+ blueprintId: string,
+ blueprintRaw: string,
+): Promise {
+ const { data } = await axios.put('/blueprints/blueprint.update', {
+ blueprintId,
+ blueprintRaw,
+ });
+ return data;
+}
+
// ────────────────────────────────────────────────────────────────────────────────
// Blueprint Metadata & Sharing
// ────────────────────────────────────────────────────────────────────────────────
diff --git a/ui/client/src/components/agentic-ai/AgentFlowGraph.tsx b/ui/client/src/components/agentic-ai/AgentFlowGraph.tsx
index 830c19a30..2f561f8a2 100644
--- a/ui/client/src/components/agentic-ai/AgentFlowGraph.tsx
+++ b/ui/client/src/components/agentic-ai/AgentFlowGraph.tsx
@@ -9,12 +9,14 @@ type AgentFlowGraphProps = {
selectedFlow: FlowObject | null;
setSelectedFlow: (flow: FlowObject | null) => void;
onValidationChange?: (isValid: boolean, validationResult: BlueprintValidationResult | null, isValidating: boolean) => void;
+ onFlowEdit?: (flow: FlowObject) => void;
};
export default function AgentFlowGraph({
selectedFlow,
setSelectedFlow,
onValidationChange,
+ onFlowEdit,
}: AgentFlowGraphProps): React.ReactElement {
const handleFlowSelect = (flow: FlowObject | null): void => {
@@ -41,9 +43,11 @@ export default function AgentFlowGraph({
selectedFlow={selectedFlow}
onFlowSelect={handleFlowSelect}
onFlowDelete={handleFlowDelete}
+ onFlowEdit={onFlowEdit}
onValidationChange={onValidationChange}
showActiveStatus={true}
showDeleteButton={true}
+ showEditButton={true}
height="100%"
graphProps={{
showBackground: true,
diff --git a/ui/client/src/components/agentic-ai/WorkflowsPanel.tsx b/ui/client/src/components/agentic-ai/WorkflowsPanel.tsx
index ec4886e2e..df61220fb 100644
--- a/ui/client/src/components/agentic-ai/WorkflowsPanel.tsx
+++ b/ui/client/src/components/agentic-ai/WorkflowsPanel.tsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import { motion } from "framer-motion";
-import { Trash2, Users } from "lucide-react";
+import { Trash2, Users, Pencil } from "lucide-react";
import { useAuth } from "@/contexts/AuthContext";
import { useShared } from "@/contexts/SharedContext";
import { Button } from "@/components/ui/button";
@@ -26,9 +26,11 @@ export interface WorkflowsPanelProps {
selectedFlow: FlowObject | null;
onFlowSelect: (flow: FlowObject | null) => void;
onFlowDelete?: (flow: FlowObject) => void;
+ onFlowEdit?: (flow: FlowObject) => void;
onValidationChange?: (isValid: boolean, validationResult: BlueprintValidationResult | null, isValidating: boolean) => void;
showActiveStatus?: boolean;
showDeleteButton?: boolean;
+ showEditButton?: boolean;
className?: string;
height?: string;
graphProps?: {
@@ -41,9 +43,11 @@ export default function WorkflowsPanel({
selectedFlow,
onFlowSelect,
onFlowDelete,
+ onFlowEdit,
onValidationChange,
showActiveStatus = false,
showDeleteButton = false,
+ showEditButton = false,
className = "",
height = "100%",
graphProps = {
@@ -193,6 +197,13 @@ export default function WorkflowsPanel({
setShowDeleteModal(true);
};
+ const handleEditClick = (flow: FlowObject, event: React.MouseEvent) => {
+ event.stopPropagation();
+ if (onFlowEdit) {
+ onFlowEdit(flow);
+ }
+ };
+
const handleShareClick = (flow: FlowObject, event: React.MouseEvent) => {
event.stopPropagation(); // Prevent flow selection when clicking share
openShareForItem({
@@ -302,6 +313,18 @@ export default function WorkflowsPanel({
Active
)}
+ {showEditButton && (
+ Edit this workflow
}>
+
+
+ )}
Share this workflow}>