diff --git a/components/backend/handlers/sessions.go b/components/backend/handlers/sessions.go
index 2e3ee5611..659dbc2bf 100644
--- a/components/backend/handlers/sessions.go
+++ b/components/backend/handlers/sessions.go
@@ -1768,13 +1768,14 @@ func fetchGitHubDirectoryListing(ctx context.Context, owner, repo, ref, path, to
// OOTBWorkflow represents an out-of-the-box workflow
type OOTBWorkflow struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Description string `json:"description"`
- GitURL string `json:"gitUrl"`
- Branch string `json:"branch"`
- Path string `json:"path,omitempty"`
- Enabled bool `json:"enabled"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ GitURL string `json:"gitUrl"`
+ Branch string `json:"branch"`
+ Path string `json:"path,omitempty"`
+ Enabled bool `json:"enabled"`
+ StartupPrompt string `json:"startupPrompt,omitempty"`
}
// ListOOTBWorkflows returns the list of out-of-the-box workflows dynamically discovered from GitHub
@@ -1883,8 +1884,9 @@ func ListOOTBWorkflows(c *gin.Context) {
ambientData, err := fetchGitHubFileContent(c.Request.Context(), owner, repoName, ootbBranch, ambientPath, token)
var ambientConfig struct {
- Name string `json:"name"`
- Description string `json:"description"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ StartupPrompt string `json:"startupPrompt"`
}
if err == nil {
// Parse ambient.json if found
@@ -1901,13 +1903,14 @@ func ListOOTBWorkflows(c *gin.Context) {
}
workflows = append(workflows, OOTBWorkflow{
- ID: entryName,
- Name: workflowName,
- Description: ambientConfig.Description,
- GitURL: ootbRepo,
- Branch: ootbBranch,
- Path: fmt.Sprintf("%s/%s", ootbWorkflowsPath, entryName),
- Enabled: true,
+ ID: entryName,
+ Name: workflowName,
+ Description: ambientConfig.Description,
+ GitURL: ootbRepo,
+ Branch: ootbBranch,
+ Path: fmt.Sprintf("%s/%s", ootbWorkflowsPath, entryName),
+ Enabled: true,
+ StartupPrompt: ambientConfig.StartupPrompt,
})
}
diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/welcome-experience.tsx b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/welcome-experience.tsx
index cdccd34dc..4f03498a5 100644
--- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/welcome-experience.tsx
+++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/components/welcome-experience.tsx
@@ -24,6 +24,7 @@ type WelcomeExperienceProps = {
hasRealMessages: boolean;
onLoadWorkflow?: () => void;
selectedWorkflow?: string;
+ workflowGreeting?: string | null;
};
const WELCOME_MESSAGE = `Welcome to Ambient AI! Please select a workflow or type a message to get started.`;
@@ -37,6 +38,7 @@ export function WelcomeExperience({
hasRealMessages,
onLoadWorkflow,
selectedWorkflow = "none",
+ workflowGreeting,
}: WelcomeExperienceProps) {
const [displayedText, setDisplayedText] = useState("");
const [isTypingComplete, setIsTypingComplete] = useState(false);
@@ -181,6 +183,26 @@ export function WelcomeExperience({
+ {/* Workflow greeting - rendered client-side after workflow activation */}
+ {workflowGreeting && (
+
+
+
+
+
+
+ {workflowGreeting}
+
+
+
+
+
+ )}
+
{/* Workflow cards - show after typing completes (only for initial phases) */}
{shouldShowWorkflowCards && isTypingComplete && enabledWorkflows.length > 0 && (
diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/hooks/use-workflow-management.ts b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/hooks/use-workflow-management.ts
index 1bf7eb409..d96ca4fa7 100644
--- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/hooks/use-workflow-management.ts
+++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/hooks/use-workflow-management.ts
@@ -22,6 +22,7 @@ export function useWorkflowManagement({
const [pendingWorkflow, setPendingWorkflow] = useState(null);
const [activeWorkflow, setActiveWorkflow] = useState(null);
const [workflowActivating, setWorkflowActivating] = useState(false);
+ const [workflowGreeting, setWorkflowGreeting] = useState(null);
// Use session queue for workflow persistence
const sessionQueue = useSessionQueue(projectName, sessionName);
@@ -85,12 +86,15 @@ export function useWorkflowManagement({
}
setActiveWorkflow(workflow.id);
+ if (workflow.startupPrompt) {
+ setWorkflowGreeting(workflow.startupPrompt);
+ }
setPendingWorkflow(null);
sessionQueue.clearWorkflow();
-
+
// Wait for restart to complete (give runner time to clone and restart)
await new Promise(resolve => setTimeout(resolve, 3000));
-
+
onWorkflowActivated?.();
setWorkflowActivating(false);
@@ -158,6 +162,7 @@ export function useWorkflowManagement({
activeWorkflow,
setActiveWorkflow,
workflowActivating,
+ workflowGreeting,
activateWorkflow,
handleWorkflowChange,
setCustomWorkflow,
diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/lib/types.ts b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/lib/types.ts
index 377e18773..7b57526af 100644
--- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/lib/types.ts
+++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/lib/types.ts
@@ -50,6 +50,7 @@ export type WorkflowConfig = {
branch: string;
path?: string;
enabled: boolean;
+ startupPrompt?: string;
};
export type WorkflowCommand = {
diff --git a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx
index 3d6b0d57f..516dd0b3f 100644
--- a/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx
+++ b/components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx
@@ -337,14 +337,17 @@ export default function ProjectSessionDetailPage({
const queuedWorkflow = workflowManagement.queuedWorkflow;
if (phase === "Running" && queuedWorkflow && !queuedWorkflow.activatedAt) {
// Session is now running, activate the queued workflow
+ // Look up the full workflow config (including startupPrompt) from the OOTB list
+ const fullWorkflow = ootbWorkflows.find(w => w.id === queuedWorkflow.id);
workflowManagement.activateWorkflow({
id: queuedWorkflow.id,
- name: "Queued workflow",
- description: "",
+ name: fullWorkflow?.name || "Queued workflow",
+ description: fullWorkflow?.description || "",
gitUrl: queuedWorkflow.gitUrl,
branch: queuedWorkflow.branch,
path: queuedWorkflow.path,
enabled: true,
+ startupPrompt: fullWorkflow?.startupPrompt,
}, phase);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -2589,6 +2592,7 @@ export default function ProjectSessionDetailPage({
hasRealMessages={hasRealMessages}
onLoadWorkflow={() => setCustomWorkflowDialogOpen(true)}
selectedWorkflow={workflowManagement.selectedWorkflow}
+ workflowGreeting={workflowManagement.workflowGreeting}
/>
}
/>
@@ -2666,6 +2670,7 @@ export default function ProjectSessionDetailPage({
hasRealMessages={hasRealMessages}
onLoadWorkflow={() => setCustomWorkflowDialogOpen(true)}
selectedWorkflow={workflowManagement.selectedWorkflow}
+ workflowGreeting={workflowManagement.workflowGreeting}
/>
}
/>
diff --git a/components/frontend/src/services/api/workflows.ts b/components/frontend/src/services/api/workflows.ts
index 54d427b7d..4ee01c8da 100644
--- a/components/frontend/src/services/api/workflows.ts
+++ b/components/frontend/src/services/api/workflows.ts
@@ -8,6 +8,7 @@ export type OOTBWorkflow = {
branch: string;
path?: string;
enabled: boolean;
+ startupPrompt?: string;
};
export type ListOOTBWorkflowsResponse = {
diff --git a/components/runners/claude-code-runner/ambient_runner/endpoints/workflow.py b/components/runners/claude-code-runner/ambient_runner/endpoints/workflow.py
index 51d22c1c6..01e928483 100644
--- a/components/runners/claude-code-runner/ambient_runner/endpoints/workflow.py
+++ b/components/runners/claude-code-runner/ambient_runner/endpoints/workflow.py
@@ -5,14 +5,10 @@
import os
import shutil
import tempfile
-import uuid
from pathlib import Path
-import aiohttp
from fastapi import APIRouter, HTTPException, Request
-from ambient_runner.platform.config import load_ambient_config
-
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -23,7 +19,7 @@
@router.post("/workflow")
async def change_workflow(request: Request):
- """Change active workflow — triggers adapter reinit and greeting."""
+ """Change active workflow — triggers adapter reinit."""
bridge = request.app.state.bridge
context = bridge.context
if not context:
@@ -42,7 +38,7 @@ async def change_workflow(request: Request):
current_path = os.getenv("ACTIVE_WORKFLOW_PATH", "").strip()
if current_git_url == git_url and current_branch == branch and current_path == path:
- logger.info("Workflow unchanged; skipping reinit and greeting")
+ logger.info("Workflow unchanged; skipping reinit")
return {"message": "Workflow already active", "gitUrl": git_url, "branch": branch, "path": path}
if git_url:
@@ -57,7 +53,6 @@ async def change_workflow(request: Request):
bridge.mark_dirty()
logger.info("Workflow updated, adapter will reinitialize on next run")
- asyncio.create_task(_trigger_workflow_greeting(git_url, branch, path, context))
return {"message": "Workflow updated", "gitUrl": git_url, "branch": branch, "path": path}
@@ -133,61 +128,3 @@ async def clone_workflow_at_runtime(git_url: str, branch: str, subpath: str) ->
finally:
if temp_dir.exists():
shutil.rmtree(temp_dir, ignore_errors=True)
-
-
-async def _trigger_workflow_greeting(git_url: str, branch: str, path: str, context):
- """Send the workflow's startupPrompt (from ambient.json) after a workflow change.
-
- If the workflow has no startupPrompt, no greeting is sent.
- """
- try:
- workspace_path = os.getenv("WORKSPACE_PATH", "/workspace")
- workflow_name = git_url.split("/")[-1].removesuffix(".git")
- if path:
- workflow_name = path.split("/")[-1]
-
- workflow_dir = str(Path(workspace_path) / "workflows" / workflow_name)
- config = load_ambient_config(workflow_dir) if Path(workflow_dir).exists() else {}
- startup_prompt = (config.get("startupPrompt") or "").strip()
-
- if not startup_prompt:
- logger.info(
- f"Workflow '{workflow_name}' has no startupPrompt in ambient.json, "
- f"skipping greeting"
- )
- return
-
- backend_url = os.getenv("BACKEND_API_URL", "").rstrip("/")
- project_name = os.getenv("AGENTIC_SESSION_NAMESPACE", "").strip()
- session_id = context.session_id if context else "unknown"
-
- if not backend_url or not project_name:
- logger.error("Cannot trigger workflow greeting: BACKEND_API_URL or PROJECT_NAME not set")
- return
-
- url = f"{backend_url}/projects/{project_name}/agentic-sessions/{session_id}/agui/run"
-
- payload = {
- "threadId": session_id,
- "runId": str(uuid.uuid4()),
- "messages": [{
- "id": str(uuid.uuid4()),
- "role": "user",
- "content": startup_prompt,
- "metadata": {"hidden": True, "autoSent": True, "source": "workflow_startup_prompt"},
- }],
- }
-
- bot_token = os.getenv("BOT_TOKEN", "").strip()
- headers = {"Content-Type": "application/json"}
- if bot_token:
- headers["Authorization"] = f"Bearer {bot_token}"
-
- async with aiohttp.ClientSession() as session:
- async with session.post(url, json=payload, headers=headers) as resp:
- if resp.status == 200:
- logger.info(f"Workflow startupPrompt sent for '{workflow_name}'")
- else:
- logger.error(f"Workflow startupPrompt failed: {resp.status} - {await resp.text()}")
- except Exception as e:
- logger.error(f"Failed to send workflow startupPrompt: {e}")