1 Commits

Author SHA1 Message Date
c0543f44ce refactor: replace personified workflow labels
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-06-03 19:04:05 +05:30
14 changed files with 104 additions and 104 deletions

View File

@@ -1,14 +1,14 @@
---
id: emily
name: Emily
role: Roleplay Agent
name: Mock Roleplay
role: Roleplay practice
service: roleplay-service
tools:
- start_roleplay_session
---
## Domain
Emily is the **Roleplay Agent**. She runs realistic workplace scenarios to help users practice conversations, negotiations, and difficult situations. She plays different personas convincingly and provides feedback.
Mock Roleplay runs realistic workplace scenarios to help users practice conversations, negotiations, and difficult situations. It plays different personas convincingly and provides feedback.
## When to use this agent (trigger phrases)
Use `start_roleplay_session` when the user:
@@ -22,11 +22,11 @@ Use `start_roleplay_session` when the user:
- Has performance situations: "performance review", "self-review", "annual review", "how to present my work"
- Needs general conversation practice: "how to say", "what should I tell", "how do I bring up", "need to tell my"
## What Emily NEVER does
- Interview practice or technical questions → Sara
- Resume writing → Resume Agent
## What Mock Roleplay NEVER does
- Interview practice or technical questions → Mock Interview
- Resume writing → Resume Building
- Job searching → Job Search Agent
- Q-Score computation → Quinn
- Q Score computation → Q Score
- Career coaching beyond roleplay → general chat
## How it works

View File

@@ -25,11 +25,11 @@ Use `prepare_application`, `track_submission`, or `schedule_followup` when the u
- Has portfolio needs: "portfolio for jobs", "work samples", "GitHub for applications", "project showcase"
## What this agent NEVER does
- Resume content optimization → Resume Agent
- Resume content optimization → Resume Building
- Job discovery → Job Search Agent
- Interview practice → Sara
- Roleplay → Emily
- Q-Score → Quinn
- Interview practice → Mock Interview
- Roleplay → Mock Roleplay
- Q Score → Q Score
## How it works
Local workflow agent managed by Rivet. Takes the shortlist from Job Search Agent and the tailored resume from Resume Agent, then prepares complete application packages including customized cover letters, tracks submission status, and manages follow-up scheduling.
Local workflow agent managed by Rivet. Takes the shortlist from Job Search Agent and the tailored resume from Resume Building, then prepares complete application packages including customized cover letters, tracks submission status, and manages follow-up scheduling.

View File

@@ -26,10 +26,10 @@ Use `search_jobs`, `rank_opportunities`, or `prepare_shortlist` when the user:
- Needs networking: "recruiter outreach", "referral strategy", "networking for jobs", "headhunter"
## What this agent NEVER does
- Resume optimization → Resume Agent
- Interview practice → Sara
- Roleplay → Emily
- Q-Score → Quinn
- Resume optimization → Resume Building
- Interview practice → Mock Interview
- Roleplay → Mock Roleplay
- Q Score → Q Score
- Application tracking → Job Apply Agent
## How it works

View File

@@ -1,7 +1,7 @@
---
id: qscore
name: Quinn
role: Q-Score Agent
name: Q Score
role: Readiness scoring
service: qscore-service
tools:
- compute_qscore
@@ -9,7 +9,7 @@ tools:
---
## Domain
Quinn is the **Q-Score Agent**. She computes and explains the user's Q-Score — a readiness score based on resume strength, interview readiness, role alignment, engagement, skills, and goal clarity. She tracks growth over time.
Q Score computes and explains the user's readiness score based on resume strength, interview readiness, role alignment, engagement, skills, and goal clarity. It tracks growth over time.
## When to use this agent (trigger phrases)
Use `ingest_signals` + `compute_qscore` when the user:
@@ -21,11 +21,11 @@ Use `ingest_signals` + `compute_qscore` when the user:
- Wants to track growth: "score trend", "progress tracking", "improvement over time", "how much have I improved"
- Mentions metrics: "quantify my readiness", "measure my growth", "score me", "rate my profile"
## What Quinn NEVER does
- Interview practice → Sara
- Roleplay scenarios → Emily
- Resume editing → Resume Agent
## What Q Score NEVER does
- Interview practice → Mock Interview
- Roleplay scenarios → Mock Roleplay
- Resume editing → Resume Building
- Job searching → Job Search Agent
## How it works
Ingests signals (resume.uploaded, resume.ats_compatibility, engagement.features_used, goals.goal_clarity) via `POST /v1/signals/ingest`, then computes Q-Score via `POST /v1/qscore/compute`. Returns score from 0-100 with breakdown across 5 pillars. If formula store unavailable, returns an estimated score from signal averages rather than failing.
Ingests signals (resume.uploaded, resume.ats_compatibility, engagement.features_used, goals.goal_clarity) via `POST /v1/signals/ingest`, then computes Q Score via `POST /v1/qscore/compute`. Returns a score from 0-100 with breakdown across 5 pillars. If the formula store is unavailable, it returns an estimated score from signal averages rather than failing.

View File

@@ -1,7 +1,7 @@
---
id: resume
name: Resume Agent
role: Resume Builder
name: Resume Building
role: Resume building
service: resume-service
tools:
- build_resume

View File

@@ -1,14 +1,14 @@
---
id: sara
name: Sara
role: Interview Agent
name: Mock Interview
role: Interview practice
service: interview-service
tools:
- start_interview_session
---
## Domain
Sara is the **Interview Agent**. She only handles job interview preparation and practice. Her focus is behavioral interviews, technical interviews, mock sessions, and interview feedback.
Mock Interview handles job interview preparation and practice. Its focus is behavioral interviews, technical interviews, mock sessions, and interview feedback.
## When to use this agent (trigger phrases)
Use `start_interview_session` when the user:
@@ -20,11 +20,11 @@ Use `start_interview_session` when the user:
- Asks about specific question types: "case interview", "product sense", "estimation questions", "leadership questions"
- Mentions any FAANG/tech company in interview context: Google, Meta, Amazon, Apple, Netflix, Microsoft, Stripe, Airbnb, Uber, etc.
## What Sara NEVER does
- Resume writing or optimization → Resume Agent
- Roleplay scenarios, negotiation, salary talk → Emily
## What Mock Interview NEVER does
- Resume writing or optimization → Resume Building
- Roleplay scenarios, negotiation, salary talk → Mock Roleplay
- Job searching or matching → Job Search Agent
- Q-Score analysis → Quinn
- Q Score analysis → Q Score
- Career switching advice → general chat
## How it works

View File

@@ -1,6 +1,6 @@
You are the Grow Agent — a unified AI orchestrator for the GrowQR platform.
You are Grow — a unified AI career assistant for the GrowQR platform.
You coordinate sub-agent capabilities (loaded as tools), maintain durable state, and execute workflows through microservices.
You coordinate specialist capabilities (loaded as tools), maintain durable state, and execute workflows through microservices.
## CRITICAL RULES
@@ -43,7 +43,7 @@ You coordinate sub-agent capabilities (loaded as tools), maintain durable state,
- After resume optimization: ask what type of interview to prepare.
- When they choose type → call start_interview_session.
- Then offer roleplay → call start_roleplay_session when they confirm.
- Then offer Q-Score → call compute_qscore.
- Then offer Q Score → call compute_qscore.
- Use [WORKFLOW: interview-to-offer] tag throughout.
## IMPORTANT: Tool Calling Anti-Patterns
@@ -66,16 +66,16 @@ Assistant: "I'll analyze your resume right away."
User: "analyze my resume"
Assistant calls analyze_resume → "Here's your analysis: [results]. Your strengths are..."
## Sub-Agent Capabilities
## Specialist Capabilities
{{MODULE_DESCRIPTIONS}}
## Workflow Tags (put at the VERY END, on their own line)
- [WORKFLOW: interview-to-offer] — full interview prep pipeline
- [WORKFLOW: interview-practice] — interview sessions with Sara
- [WORKFLOW: interview-practice] — mock interview sessions
- [WORKFLOW: resume-boost] — resume analysis and optimization
- [WORKFLOW: roleplay-practice] — roleplay sessions with Emily
- [WORKFLOW: roleplay-practice] — mock roleplay sessions
- [WORKFLOW: career-switch] — career change navigation
- [WORKFLOW: job-search] — job discovery
- [WORKFLOW: job-preparation] — broad company preparation

View File

@@ -179,7 +179,7 @@ function buildUnifiedTools(): Array<{
type: "function" as const,
function: {
name: "start_interview_session",
description: "Create a real interview practice session via the Sara / interview-service microservice.",
description: "Create a real mock interview session via the interview-service microservice.",
parameters: {
type: "object",
properties: { goal: { type: "string" } },
@@ -191,7 +191,7 @@ function buildUnifiedTools(): Array<{
type: "function" as const,
function: {
name: "start_roleplay_session",
description: "Create a real roleplay practice session via the Emily / roleplay-service microservice.",
description: "Create a real mock roleplay session via the roleplay-service microservice.",
parameters: {
type: "object",
properties: { goal: { type: "string" } },
@@ -203,7 +203,7 @@ function buildUnifiedTools(): Array<{
type: "function" as const,
function: {
name: "compute_qscore",
description: "Compute or refresh the user's Q-Score via the Quinn / qscore-service microservice.",
description: "Compute or refresh the user's Q Score via the qscore-service microservice.",
parameters: {
type: "object",
properties: {},
@@ -215,7 +215,7 @@ function buildUnifiedTools(): Array<{
type: "function" as const,
function: {
name: "analyze_resume",
description: "Analyze the user's resume using the Resume Agent microservice. Returns completeness score, skill gaps, and optimization recommendations.",
description: "Analyze the user's resume using the Resume Building microservice. Returns completeness score, skill gaps, and optimization recommendations.",
parameters: {
type: "object",
properties: {
@@ -243,7 +243,7 @@ function buildUnifiedTools(): Array<{
type: "function" as const,
function: {
name: "start_interview_to_offer",
description: "Start the Interview-to-Offer Accelerator workflow. This is a guided end-to-end pipeline: (1) Analyze & tailor resume for the role, (2) Create interview practice session with Sara, (3) Create roleplay session with Emily, (4) Compute Q-Score readiness. Use this when the user has a specific interview scheduled and wants comprehensive preparation.",
description: "Start the Interview-to-Offer Accelerator workflow. This is a guided end-to-end pipeline: (1) Analyze and tailor the resume for the role, (2) Create mock interview practice, (3) Create mock roleplay practice, and (4) Compute Q Score readiness. Use this when the user has a specific interview scheduled and wants comprehensive preparation.",
parameters: {
type: "object",
properties: {
@@ -555,7 +555,7 @@ export const userActor = actor({
appendTimelineEvent(
c.state,
{ id: "grow", name: "Grow Agent" },
{ id: "grow", name: "Grow" },
"workflow",
"Job application workflow started.",
);
@@ -573,14 +573,14 @@ export const userActor = actor({
pauseWorkflow: async (c) => {
c.state.workflowStatus = "paused";
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "workflow", "Workflow paused.");
appendTimelineEvent(c.state, { id: "grow", name: "Grow" }, "workflow", "Workflow paused.");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
return c.state;
},
resumeWorkflow: async (c) => {
c.state.workflowStatus = "running";
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "workflow", "Workflow resumed.");
appendTimelineEvent(c.state, { id: "grow", name: "Grow" }, "workflow", "Workflow resumed.");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
return c.state;
},
@@ -722,7 +722,7 @@ async function dispatchUnifiedTool(
c.state.modules = makeModules();
c.state.createdAt = now();
c.state.updatedAt = now();
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "workflow", "Workflow started via LLM tool.");
appendTimelineEvent(c.state, { id: "grow", name: "Grow" }, "workflow", "Workflow started via LLM tool.");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
return { ok: true, workflowId: c.state.workflowId, goal };
}
@@ -756,7 +756,7 @@ async function dispatchUnifiedTool(
case "start_interview_session": {
const goal = String(input.goal ?? "");
const saraModule = getSubAgentModule("sara");
if (!saraModule?.service) return { ok: false, error: "Sara module not available" };
if (!saraModule?.service) return { ok: false, error: "Mock Interview module not available" };
const result = await runServiceAgentProbe(
{ id: saraModule.id, name: saraModule.name, role: saraModule.role, kind: "microservice", description: saraModule.description, service: saraModule.service },
{ userId, goal },
@@ -768,7 +768,7 @@ async function dispatchUnifiedTool(
case "start_roleplay_session": {
const goal = String(input.goal ?? "");
const emilyModule = getSubAgentModule("emily");
if (!emilyModule?.service) return { ok: false, error: "Emily module not available" };
if (!emilyModule?.service) return { ok: false, error: "Mock Roleplay module not available" };
const result = await runServiceAgentProbe(
{ id: emilyModule.id, name: emilyModule.name, role: emilyModule.role, kind: "microservice", description: emilyModule.description, service: emilyModule.service },
{ userId, goal },
@@ -779,7 +779,7 @@ async function dispatchUnifiedTool(
case "compute_qscore": {
const quinnModule = getSubAgentModule("qscore");
if (!quinnModule?.service) return { ok: false, error: "Quinn module not available" };
if (!quinnModule?.service) return { ok: false, error: "Q Score module not available" };
const result = await runServiceAgentProbe(
{ id: quinnModule.id, name: quinnModule.name, role: quinnModule.role, kind: "score", description: quinnModule.description, service: quinnModule.service },
{ userId, goal: c.state.workflowGoal || "general assessment" },
@@ -824,14 +824,14 @@ async function dispatchUnifiedTool(
c.state.createdAt = now();
c.state.updatedAt = now();
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "workflow", `Interview-to-Offer workflow started for: ${goal}`);
appendTimelineEvent(c.state, { id: "grow", name: "Grow" }, "workflow", `Interview-to-Offer workflow started for: ${goal}`);
// Step 1: Resume Agent — analyze and tailor
// Step 1: Resume Building — analyze and tailor
const resumeModule = getSubAgentModule("resume");
const resumeMod = c.state.modules.find(m => m.id === "resume");
if (resumeMod && resumeModule) {
resumeMod.status = "running";
appendTimelineEvent(c.state, resumeMod, "module", "Resume Agent analyzing your profile...");
appendTimelineEvent(c.state, resumeMod, "module", "Resume Building is analyzing your profile...");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
try {
@@ -844,18 +844,18 @@ async function dispatchUnifiedTool(
appendTimelineEvent(c.state, resumeMod, "module", resumeResult.summary);
} catch (err) {
resumeMod.status = "blocked";
appendTimelineEvent(c.state, resumeMod, "module", `Resume Agent failed: ${err instanceof Error ? err.message : String(err)}`);
appendTimelineEvent(c.state, resumeMod, "module", `Resume Building failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
c.broadcast("workflow.updated", workflowSnapshot(c.state));
// Step 2: Sara — create interview session
// Step 2: Mock Interview — create interview session
const saraModule = getSubAgentModule("sara");
const saraMod = c.state.modules.find(m => m.id === "sara");
if (saraMod && saraModule?.service) {
saraMod.status = "running";
appendTimelineEvent(c.state, saraMod, "module", "Sara creating interview practice session...");
appendTimelineEvent(c.state, saraMod, "module", "Mock Interview is creating an interview practice session...");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
try {
@@ -868,18 +868,18 @@ async function dispatchUnifiedTool(
appendTimelineEvent(c.state, saraMod, "module", saraResult.summary);
} catch (err) {
saraMod.status = "blocked";
appendTimelineEvent(c.state, saraMod, "module", `Sara session failed: ${err instanceof Error ? err.message : String(err)}`);
appendTimelineEvent(c.state, saraMod, "module", `Mock Interview session failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
c.broadcast("workflow.updated", workflowSnapshot(c.state));
// Step 3: Emily — create roleplay session
// Step 3: Mock Roleplay — create roleplay session
const emilyModule = getSubAgentModule("emily");
const emilyMod = c.state.modules.find(m => m.id === "emily");
if (emilyMod && emilyModule?.service) {
emilyMod.status = "running";
appendTimelineEvent(c.state, emilyMod, "module", "Emily creating roleplay scenario...");
appendTimelineEvent(c.state, emilyMod, "module", "Mock Roleplay is creating a practice scenario...");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
try {
@@ -892,18 +892,18 @@ async function dispatchUnifiedTool(
appendTimelineEvent(c.state, emilyMod, "module", emilyResult.summary);
} catch (err) {
emilyMod.status = "blocked";
appendTimelineEvent(c.state, emilyMod, "module", `Emily session failed: ${err instanceof Error ? err.message : String(err)}`);
appendTimelineEvent(c.state, emilyMod, "module", `Mock Roleplay session failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
c.broadcast("workflow.updated", workflowSnapshot(c.state));
// Step 4: Quinn — compute Q-Score
// Step 4: Q Score — compute readiness
const quinnModule = getSubAgentModule("qscore");
const quinnMod = c.state.modules.find(m => m.id === "qscore");
if (quinnMod && quinnModule?.service) {
quinnMod.status = "running";
appendTimelineEvent(c.state, quinnMod, "module", "Quinn computing your readiness Q-Score...");
appendTimelineEvent(c.state, quinnMod, "module", "Q Score is computing your readiness score...");
c.broadcast("workflow.updated", workflowSnapshot(c.state));
try {
@@ -916,7 +916,7 @@ async function dispatchUnifiedTool(
appendTimelineEvent(c.state, quinnMod, "module", quinnResult.summary);
} catch (err) {
quinnMod.status = "blocked";
appendTimelineEvent(c.state, quinnMod, "module", `Q-Score computation failed: ${err instanceof Error ? err.message : String(err)}`);
appendTimelineEvent(c.state, quinnMod, "module", `Q Score computation failed: ${err instanceof Error ? err.message : String(err)}`);
}
}

View File

@@ -45,7 +45,7 @@ export function jobApplicationModuleIds(): string[] {
return loaderJobApplicationModuleIds();
}
// Build the unified Grow Agent system prompt from disk (changes.md §3).
// Build the unified Grow system prompt from disk (changes.md §3).
export function buildUnifiedSystemPrompt(): string {
return getUnifiedSystemPrompt();
}

View File

@@ -25,7 +25,7 @@ export const requireUser = createMiddleware<AuthContext>(async (c, next) => {
const auth = c.req.header("authorization") ?? "";
const token = auth.replace(/^Bearer\s+/i, "").trim();
// Service-to-service path (Grow Agent actor calling backend).
// Service-to-service path (Grow stack calling backend).
// Header `x-growqr-user` is REQUIRED so we can scope the call.
if (
token &&

View File

@@ -164,7 +164,7 @@ export async function loadPromptsFromDisk(): Promise<void> {
} catch (err) {
log.error({ err, path: SYSTEM_PROMPT_FILE }, "failed to load system prompt — using fallback");
// Fallback: assemble from modules without a template file.
const fallback = `You are the Grow Agent — a unified AI orchestrator for the GrowQR platform.\n\n## Sub-Agent Capabilities\n\n${modules.map((m) => `- **${m.name}**: ${m.description}`).join("\n")}`;
const fallback = `You are Grow — a unified AI career assistant for the GrowQR platform.\n\n## Specialist Capabilities\n\n${modules.map((m) => `- **${m.name}**: ${m.description}`).join("\n")}`;
cachedSystemPrompt = fallback;
}
}

View File

@@ -40,7 +40,7 @@ function buildTools() {
type: "function" as const,
function: {
name: "start_interview_session",
description: "Create a real interview practice session via the Sara / interview-service microservice. Call this when the user asks to start or launch an interview.",
description: "Create a real mock interview session via the interview-service microservice. Call this when the user asks to start or launch interview practice.",
parameters: {
type: "object",
properties: {
@@ -54,7 +54,7 @@ function buildTools() {
type: "function" as const,
function: {
name: "start_roleplay_session",
description: "Create a real roleplay session via Emily / roleplay-service. Call when user asks for roleplay or negotiation practice.",
description: "Create a real mock roleplay session via roleplay-service. Call when the user asks for roleplay or negotiation practice.",
parameters: {
type: "object",
properties: {
@@ -68,7 +68,7 @@ function buildTools() {
type: "function" as const,
function: {
name: "analyze_resume",
description: "Analyze user's resume using the Resume Agent. Returns completeness, skills, and gaps.",
description: "Analyze the user's resume using Resume Building. Returns completeness, skills, and gaps.",
parameters: {
type: "object",
properties: {
@@ -82,7 +82,7 @@ function buildTools() {
type: "function" as const,
function: {
name: "compute_qscore",
description: "Compute user's readiness Q-Score via Quinn / qscore-service.",
description: "Compute the user's readiness Q Score via qscore-service.",
parameters: {
type: "object",
properties: {},
@@ -201,14 +201,14 @@ export function chatRoutes() {
switch (toolCall.name) {
case "start_interview_session": {
toolResult = await runServiceAgentProbe(
{ id: "sara", name: "Sara", role: "Interview Agent", kind: "microservice", description: "Interview practice", service: "interview-service" },
{ id: "sara", name: "Mock Interview", role: "Interview practice", kind: "microservice", description: "Interview practice", service: "interview-service" },
{ userId, goal: String(toolCall.arguments.target_role ?? "general preparation") },
);
if (toolResult.status === "ok" && toolResult.detail) {
const detail = toolResult.detail as Record<string, unknown>;
sessions.push({
moduleId: "sara",
moduleName: "Sara",
moduleName: "Mock Interview",
status: "done",
sessionId: detail.session_id as string,
sessionUrl: typeof detail.ui_session_url === "string"
@@ -221,14 +221,14 @@ export function chatRoutes() {
}
case "start_roleplay_session": {
toolResult = await runServiceAgentProbe(
{ id: "emily", name: "Emily", role: "Roleplay Agent", kind: "microservice", description: "Roleplay practice", service: "roleplay-service" },
{ id: "emily", name: "Mock Roleplay", role: "Roleplay practice", kind: "microservice", description: "Roleplay practice", service: "roleplay-service" },
{ userId, goal: String(toolCall.arguments.goal ?? "general practice") },
);
if (toolResult.status === "ok" && toolResult.detail) {
const detail = toolResult.detail as Record<string, unknown>;
sessions.push({
moduleId: "emily",
moduleName: "Emily",
moduleName: "Mock Roleplay",
status: "done",
sessionId: detail.session_id as string,
sessionUrl: typeof detail.ui_session_url === "string"
@@ -241,14 +241,14 @@ export function chatRoutes() {
}
case "analyze_resume": {
toolResult = await runServiceAgentProbe(
{ id: "resume", name: "Resume Agent", role: "Resume Builder", kind: "microservice", description: "Resume analysis", service: "resume-service" },
{ id: "resume", name: "Resume Building", role: "Resume building", kind: "microservice", description: "Resume analysis", service: "resume-service" },
{ userId, goal: String(toolCall.arguments.goal ?? "general") },
);
if (toolResult.status === "ok") {
const detail = toolResult.detail as Record<string, unknown> | undefined;
sessions.push({
moduleId: "resume",
moduleName: "Resume Agent",
moduleName: "Resume Building",
status: "done",
sessionUrl: typeof detail?.ui_session_url === "string"
? detail.ui_session_url
@@ -260,11 +260,11 @@ export function chatRoutes() {
}
case "compute_qscore": {
toolResult = await runServiceAgentProbe(
{ id: "qscore", name: "Quinn", role: "Q-Score Agent", kind: "score", description: "Readiness scoring", service: "qscore-service" },
{ id: "qscore", name: "Q Score", role: "Readiness scoring", kind: "score", description: "Readiness scoring", service: "qscore-service" },
{ userId, goal: "general assessment" },
);
if (toolResult.status === "ok") {
sessions.push({ moduleId: "qscore", moduleName: "Quinn", status: "done", summary: toolResult.summary });
sessions.push({ moduleId: "qscore", moduleName: "Q Score", status: "done", summary: toolResult.summary });
}
break;
}

View File

@@ -12,7 +12,7 @@ export function userRoutes() {
// Called by the frontend right after Clerk sign-in.
// - Ensures a `users` row exists (the auth middleware already lazy-mirrors).
// - Kicks off Grow Agent stack provisioning if not already running.
// - Kicks off the Grow stack provisioning if not already running.
// - Returns the current stack status so the UI can render a provisioning spinner.
app.post("/bootstrap", async (c) => {
const userId = c.get("userId");

View File

@@ -105,7 +105,7 @@ async function healthCheck(baseUrl: string, label: string): Promise<ServiceAgent
}
}
async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
async function runInterviewPractice(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
const payload = {
user_id: ctx.userId,
org_id: ctx.orgId ?? "growqr",
@@ -129,7 +129,7 @@ async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentR
);
return {
status: "ok",
summary: `Sara created interview session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
summary: `Mock Interview created session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
detail: {
...detail,
target_role: payload.context.target_role,
@@ -139,7 +139,7 @@ async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentR
};
}
async function runEmilyRoleplay(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
async function runRoleplayPractice(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
const payload = {
user_id: ctx.userId,
org_id: ctx.orgId ?? "growqr",
@@ -173,7 +173,7 @@ async function runEmilyRoleplay(ctx: ServiceAgentContext): Promise<ServiceAgentR
);
return {
status: "ok",
summary: `Emily created roleplay session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
summary: `Mock Roleplay created session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
detail: {
...detail,
target_role: payload.metadata.target_role,
@@ -183,7 +183,7 @@ async function runEmilyRoleplay(ctx: ServiceAgentContext): Promise<ServiceAgentR
};
}
async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
async function runQScoreCheck(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
const orgId = ctx.orgId ?? "growqr";
const qscoreUserId = stableUuid(ctx.userId);
const signals = [
@@ -191,25 +191,25 @@ async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentRes
signal_id: "resume.uploaded",
present: true,
score: 82,
raw: { source: "resume-agent", workflow_goal: ctx.goal },
raw: { source: "resume-building", workflow_goal: ctx.goal },
},
{
signal_id: "resume.ats_compatibility",
present: true,
score: 76,
raw: { source: "resume-agent", workflow_goal: ctx.goal },
raw: { source: "resume-building", workflow_goal: ctx.goal },
},
{
signal_id: "engagement.features_used",
present: true,
score: 88,
raw: { source: "grow-agent", workflow_goal: ctx.goal },
raw: { source: "grow", workflow_goal: ctx.goal },
},
{
signal_id: "goals.goal_clarity",
present: true,
score: 90,
raw: { source: "grow-agent", workflow_goal: ctx.goal },
raw: { source: "grow", workflow_goal: ctx.goal },
},
];
@@ -235,7 +235,7 @@ async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentRes
ingest = { status: "skipped", reason: err instanceof Error ? err.message : String(err) };
}
// Try to compute Q-Score
// Try to compute Q Score
let compute: Record<string, unknown> | undefined;
try {
compute = await serviceJson<Record<string, unknown>>(
@@ -256,7 +256,7 @@ async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentRes
);
return {
status: "ok",
summary: `Quinn estimated Q-Score ~${avgSignalScore} (service compute unavailable: formula store may not be seeded). Based on ${signals.length} signals.`,
summary: `Q Score estimated ~${avgSignalScore} (service compute unavailable: formula store may not be seeded). Based on ${signals.length} signals.`,
detail: {
ingest,
estimated_q_score: avgSignalScore,
@@ -269,12 +269,12 @@ async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentRes
return {
status: "ok",
summary: `Quinn computed Q-Score ${compute.q_score ?? "(unknown)"} for ${ctx.goal}.`,
summary: `Q Score computed ${compute.q_score ?? "(unknown)"} for ${ctx.goal}.`,
detail: { ingest, compute, qscore_user_id: qscoreUserId },
};
}
// ── Resume Agent (resume-builder service from growqr-app) ──
// ── Resume Building (resume-builder service from growqr-app) ──
async function runResumeAnalyze(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
// Probe resume state for the user
@@ -289,8 +289,8 @@ async function runResumeAnalyze(ctx: ServiceAgentContext): Promise<ServiceAgentR
return {
status: "ok",
summary: hasResume
? `Resume Agent found ${detail.resume_count} resume(s) at ${completeness}% completeness. Current role: ${detail.current_role ?? "unknown"}.`
: "No existing resume found. Resume Agent is ready to build one from scratch.",
? `Resume Building found ${detail.resume_count} resume(s) at ${completeness}% completeness. Current role: ${detail.current_role ?? "unknown"}.`
: "No existing resume found. Resume Building is ready to build one from scratch.",
detail: {
resume_count: detail.resume_count,
completeness,
@@ -302,7 +302,7 @@ async function runResumeAnalyze(ctx: ServiceAgentContext): Promise<ServiceAgentR
} catch (err) {
return {
status: "unavailable",
summary: `Resume Agent unavailable: ${err instanceof Error ? err.message : String(err)}`,
summary: `Resume Building unavailable: ${err instanceof Error ? err.message : String(err)}`,
};
}
}
@@ -317,7 +317,7 @@ async function runResumeTailor(ctx: ServiceAgentContext): Promise<ServiceAgentRe
// Return summary with optimization guidance
return {
status: "ok",
summary: `Resume Agent analyzed your profile for the role "${ctx.goal}". Skills detected: ${(stateResult.detail as any)?.skills?.slice(0, 5).join(", ") ?? "none"}. Resume ready for optimization.`,
summary: `Resume Building analyzed your profile for the role "${ctx.goal}". Skills detected: ${(stateResult.detail as any)?.skills?.slice(0, 5).join(", ") ?? "none"}. Resume is ready for optimization.`,
detail: {
...(stateResult.detail as Record<string, unknown> ?? {}),
goal: ctx.goal,
@@ -381,20 +381,20 @@ export async function runServiceAgentProbe(
switch (agent.service) {
case "interview-service":
return ctx
? await runSaraInterview(ctx)
: healthCheck(config.interviewServiceUrl, "Sara / interview-service");
? await runInterviewPractice(ctx)
: healthCheck(config.interviewServiceUrl, "Mock Interview / interview-service");
case "roleplay-service":
return ctx
? await runEmilyRoleplay(ctx)
: healthCheck(config.roleplayServiceUrl, "Emily / roleplay-service");
? await runRoleplayPractice(ctx)
: healthCheck(config.roleplayServiceUrl, "Mock Roleplay / roleplay-service");
case "qscore-service":
return ctx
? await runQuinnQScore(ctx)
: healthCheck(config.qscoreServiceUrl, "Quinn / qscore-service");
? await runQScoreCheck(ctx)
: healthCheck(config.qscoreServiceUrl, "Q Score / qscore-service");
case "resume-service":
return ctx
? await runResumeTailor(ctx)
: healthCheck(config.resumeServiceUrl, "Resume Agent / resume-service");
: healthCheck(config.resumeServiceUrl, "Resume Building / resume-service");
case "matchmaking-service":
return ctx
? await runMatchmaking(ctx)