Compare commits
7 Commits
subagents-
...
feat/integ
| Author | SHA1 | Date | |
|---|---|---|---|
| 94d826347b | |||
| dd6ccf1141 | |||
| 0a9bbe6b6f | |||
| 4d284b58d7 | |||
| e48c19b840 | |||
| 54297496a4 | |||
| 2d471c61b4 |
@@ -1,14 +1,14 @@
|
||||
---
|
||||
id: emily
|
||||
name: Mock Roleplay
|
||||
role: Roleplay practice
|
||||
name: Emily
|
||||
role: Roleplay Agent
|
||||
service: roleplay-service
|
||||
tools:
|
||||
- start_roleplay_session
|
||||
---
|
||||
|
||||
## Domain
|
||||
Mock Roleplay runs realistic workplace scenarios to help users practice conversations, negotiations, and difficult situations. It plays different personas convincingly and provides feedback.
|
||||
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.
|
||||
|
||||
## 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 Mock Roleplay NEVER does
|
||||
- Interview practice or technical questions → Mock Interview
|
||||
- Resume writing → Resume Building
|
||||
## What Emily NEVER does
|
||||
- Interview practice or technical questions → Sara
|
||||
- Resume writing → Resume Agent
|
||||
- Job searching → Job Search Agent
|
||||
- Q Score computation → Q Score
|
||||
- Q-Score computation → Quinn
|
||||
- Career coaching beyond roleplay → general chat
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -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 Building
|
||||
- Resume content optimization → Resume Agent
|
||||
- Job discovery → Job Search Agent
|
||||
- Interview practice → Mock Interview
|
||||
- Roleplay → Mock Roleplay
|
||||
- Q Score → Q Score
|
||||
- Interview practice → Sara
|
||||
- Roleplay → Emily
|
||||
- Q-Score → Quinn
|
||||
|
||||
## How it works
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -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 Building
|
||||
- Interview practice → Mock Interview
|
||||
- Roleplay → Mock Roleplay
|
||||
- Q Score → Q Score
|
||||
- Resume optimization → Resume Agent
|
||||
- Interview practice → Sara
|
||||
- Roleplay → Emily
|
||||
- Q-Score → Quinn
|
||||
- Application tracking → Job Apply Agent
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: qscore
|
||||
name: Q Score
|
||||
role: Readiness scoring
|
||||
name: Quinn
|
||||
role: Q-Score Agent
|
||||
service: qscore-service
|
||||
tools:
|
||||
- compute_qscore
|
||||
@@ -9,7 +9,7 @@ tools:
|
||||
---
|
||||
|
||||
## Domain
|
||||
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.
|
||||
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.
|
||||
|
||||
## 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 Q Score NEVER does
|
||||
- Interview practice → Mock Interview
|
||||
- Roleplay scenarios → Mock Roleplay
|
||||
- Resume editing → Resume Building
|
||||
## What Quinn NEVER does
|
||||
- Interview practice → Sara
|
||||
- Roleplay scenarios → Emily
|
||||
- Resume editing → Resume Agent
|
||||
- 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 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.
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: resume
|
||||
name: Resume Building
|
||||
role: Resume building
|
||||
name: Resume Agent
|
||||
role: Resume Builder
|
||||
service: resume-service
|
||||
tools:
|
||||
- build_resume
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
id: sara
|
||||
name: Mock Interview
|
||||
role: Interview practice
|
||||
name: Sara
|
||||
role: Interview Agent
|
||||
service: interview-service
|
||||
tools:
|
||||
- start_interview_session
|
||||
---
|
||||
|
||||
## Domain
|
||||
Mock Interview handles job interview preparation and practice. Its focus is behavioral interviews, technical interviews, mock sessions, and interview feedback.
|
||||
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.
|
||||
|
||||
## 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 Mock Interview NEVER does
|
||||
- Resume writing or optimization → Resume Building
|
||||
- Roleplay scenarios, negotiation, salary talk → Mock Roleplay
|
||||
## What Sara NEVER does
|
||||
- Resume writing or optimization → Resume Agent
|
||||
- Roleplay scenarios, negotiation, salary talk → Emily
|
||||
- Job searching or matching → Job Search Agent
|
||||
- Q Score analysis → Q Score
|
||||
- Q-Score analysis → Quinn
|
||||
- Career switching advice → general chat
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
You are Grow — a unified AI career assistant for the GrowQR platform.
|
||||
You are the Grow Agent — a unified AI orchestrator for the GrowQR platform.
|
||||
|
||||
You coordinate specialist capabilities (loaded as tools), maintain durable state, and execute workflows through microservices.
|
||||
You coordinate sub-agent capabilities (loaded as tools), maintain durable state, and execute workflows through microservices.
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
@@ -43,7 +43,7 @@ You coordinate specialist 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..."
|
||||
|
||||
## Specialist Capabilities
|
||||
## Sub-Agent 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] — mock interview sessions
|
||||
- [WORKFLOW: interview-practice] — interview sessions with Sara
|
||||
- [WORKFLOW: resume-boost] — resume analysis and optimization
|
||||
- [WORKFLOW: roleplay-practice] — mock roleplay sessions
|
||||
- [WORKFLOW: roleplay-practice] — roleplay sessions with Emily
|
||||
- [WORKFLOW: career-switch] — career change navigation
|
||||
- [WORKFLOW: job-search] — job discovery
|
||||
- [WORKFLOW: job-preparation] — broad company preparation
|
||||
|
||||
@@ -179,7 +179,7 @@ function buildUnifiedTools(): Array<{
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: "start_interview_session",
|
||||
description: "Create a real mock interview session via the interview-service microservice.",
|
||||
description: "Create a real interview practice session via the Sara / 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 mock roleplay session via the roleplay-service microservice.",
|
||||
description: "Create a real roleplay practice session via the Emily / 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 qscore-service microservice.",
|
||||
description: "Compute or refresh the user's Q-Score via the Quinn / 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 Building microservice. Returns completeness score, skill gaps, and optimization recommendations.",
|
||||
description: "Analyze the user's resume using the Resume Agent 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 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.",
|
||||
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.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -555,7 +555,7 @@ export const userActor = actor({
|
||||
|
||||
appendTimelineEvent(
|
||||
c.state,
|
||||
{ id: "grow", name: "Grow" },
|
||||
{ id: "grow", name: "Grow Agent" },
|
||||
"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" }, "workflow", "Workflow paused.");
|
||||
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "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" }, "workflow", "Workflow resumed.");
|
||||
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "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" }, "workflow", "Workflow started via LLM tool.");
|
||||
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "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: "Mock Interview module not available" };
|
||||
if (!saraModule?.service) return { ok: false, error: "Sara 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: "Mock Roleplay module not available" };
|
||||
if (!emilyModule?.service) return { ok: false, error: "Emily 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: "Q Score module not available" };
|
||||
if (!quinnModule?.service) return { ok: false, error: "Quinn 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" }, "workflow", `Interview-to-Offer workflow started for: ${goal}`);
|
||||
appendTimelineEvent(c.state, { id: "grow", name: "Grow Agent" }, "workflow", `Interview-to-Offer workflow started for: ${goal}`);
|
||||
|
||||
// Step 1: Resume Building — analyze and tailor
|
||||
// Step 1: Resume Agent — 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 Building is analyzing your profile...");
|
||||
appendTimelineEvent(c.state, resumeMod, "module", "Resume Agent 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 Building failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
appendTimelineEvent(c.state, resumeMod, "module", `Resume Agent failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
c.broadcast("workflow.updated", workflowSnapshot(c.state));
|
||||
|
||||
// Step 2: Mock Interview — create interview session
|
||||
// Step 2: Sara — 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", "Mock Interview is creating an interview practice session...");
|
||||
appendTimelineEvent(c.state, saraMod, "module", "Sara creating 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", `Mock Interview session failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
appendTimelineEvent(c.state, saraMod, "module", `Sara session failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
c.broadcast("workflow.updated", workflowSnapshot(c.state));
|
||||
|
||||
// Step 3: Mock Roleplay — create roleplay session
|
||||
// Step 3: Emily — 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", "Mock Roleplay is creating a practice scenario...");
|
||||
appendTimelineEvent(c.state, emilyMod, "module", "Emily creating roleplay 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", `Mock Roleplay session failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
appendTimelineEvent(c.state, emilyMod, "module", `Emily session failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
c.broadcast("workflow.updated", workflowSnapshot(c.state));
|
||||
|
||||
// Step 4: Q Score — compute readiness
|
||||
// Step 4: Quinn — compute Q-Score
|
||||
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", "Q Score is computing your readiness score...");
|
||||
appendTimelineEvent(c.state, quinnMod, "module", "Quinn computing your readiness Q-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)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export function jobApplicationModuleIds(): string[] {
|
||||
return loaderJobApplicationModuleIds();
|
||||
}
|
||||
|
||||
// Build the unified Grow system prompt from disk (changes.md §3).
|
||||
// Build the unified Grow Agent system prompt from disk (changes.md §3).
|
||||
export function buildUnifiedSystemPrompt(): string {
|
||||
return getUnifiedSystemPrompt();
|
||||
}
|
||||
|
||||
@@ -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 stack calling backend).
|
||||
// Service-to-service path (Grow Agent actor calling backend).
|
||||
// Header `x-growqr-user` is REQUIRED so we can scope the call.
|
||||
if (
|
||||
token &&
|
||||
|
||||
@@ -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 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")}`;
|
||||
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")}`;
|
||||
cachedSystemPrompt = fallback;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function buildTools() {
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: "start_interview_session",
|
||||
description: "Create a real mock interview session via the interview-service microservice. Call this when the user asks to start or launch interview practice.",
|
||||
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.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -54,7 +54,7 @@ function buildTools() {
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: "start_roleplay_session",
|
||||
description: "Create a real mock roleplay session via roleplay-service. Call when the user asks for roleplay or negotiation practice.",
|
||||
description: "Create a real roleplay session via Emily / roleplay-service. Call when 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 the user's resume using Resume Building. Returns completeness, skills, and gaps.",
|
||||
description: "Analyze user's resume using the Resume Agent. 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 the user's readiness Q Score via qscore-service.",
|
||||
description: "Compute user's readiness Q-Score via Quinn / 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: "Mock Interview", role: "Interview practice", kind: "microservice", description: "Interview practice", service: "interview-service" },
|
||||
{ id: "sara", name: "Sara", role: "Interview Agent", 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: "Mock Interview",
|
||||
moduleName: "Sara",
|
||||
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: "Mock Roleplay", role: "Roleplay practice", kind: "microservice", description: "Roleplay practice", service: "roleplay-service" },
|
||||
{ id: "emily", name: "Emily", role: "Roleplay Agent", 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: "Mock Roleplay",
|
||||
moduleName: "Emily",
|
||||
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 Building", role: "Resume building", kind: "microservice", description: "Resume analysis", service: "resume-service" },
|
||||
{ id: "resume", name: "Resume Agent", role: "Resume Builder", 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 Building",
|
||||
moduleName: "Resume Agent",
|
||||
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: "Q Score", role: "Readiness scoring", kind: "score", description: "Readiness scoring", service: "qscore-service" },
|
||||
{ id: "qscore", name: "Quinn", role: "Q-Score Agent", kind: "score", description: "Readiness scoring", service: "qscore-service" },
|
||||
{ userId, goal: "general assessment" },
|
||||
);
|
||||
if (toolResult.status === "ok") {
|
||||
sessions.push({ moduleId: "qscore", moduleName: "Q Score", status: "done", summary: toolResult.summary });
|
||||
sessions.push({ moduleId: "qscore", moduleName: "Quinn", status: "done", summary: toolResult.summary });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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 the Grow stack provisioning if not already running.
|
||||
// - Kicks off Grow Agent 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");
|
||||
|
||||
@@ -105,7 +105,7 @@ async function healthCheck(baseUrl: string, label: string): Promise<ServiceAgent
|
||||
}
|
||||
}
|
||||
|
||||
async function runInterviewPractice(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
const payload = {
|
||||
user_id: ctx.userId,
|
||||
org_id: ctx.orgId ?? "growqr",
|
||||
@@ -129,7 +129,7 @@ async function runInterviewPractice(ctx: ServiceAgentContext): Promise<ServiceAg
|
||||
);
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Mock Interview created session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
summary: `Sara created interview session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
detail: {
|
||||
...detail,
|
||||
target_role: payload.context.target_role,
|
||||
@@ -139,7 +139,7 @@ async function runInterviewPractice(ctx: ServiceAgentContext): Promise<ServiceAg
|
||||
};
|
||||
}
|
||||
|
||||
async function runRoleplayPractice(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
async function runEmilyRoleplay(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
const payload = {
|
||||
user_id: ctx.userId,
|
||||
org_id: ctx.orgId ?? "growqr",
|
||||
@@ -173,7 +173,7 @@ async function runRoleplayPractice(ctx: ServiceAgentContext): Promise<ServiceAge
|
||||
);
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Mock Roleplay created session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
summary: `Emily created roleplay session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
detail: {
|
||||
...detail,
|
||||
target_role: payload.metadata.target_role,
|
||||
@@ -183,7 +183,7 @@ async function runRoleplayPractice(ctx: ServiceAgentContext): Promise<ServiceAge
|
||||
};
|
||||
}
|
||||
|
||||
async function runQScoreCheck(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
async function runQuinnQScore(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
const orgId = ctx.orgId ?? "growqr";
|
||||
const qscoreUserId = stableUuid(ctx.userId);
|
||||
const signals = [
|
||||
@@ -191,25 +191,25 @@ async function runQScoreCheck(ctx: ServiceAgentContext): Promise<ServiceAgentRes
|
||||
signal_id: "resume.uploaded",
|
||||
present: true,
|
||||
score: 82,
|
||||
raw: { source: "resume-building", workflow_goal: ctx.goal },
|
||||
raw: { source: "resume-agent", workflow_goal: ctx.goal },
|
||||
},
|
||||
{
|
||||
signal_id: "resume.ats_compatibility",
|
||||
present: true,
|
||||
score: 76,
|
||||
raw: { source: "resume-building", workflow_goal: ctx.goal },
|
||||
raw: { source: "resume-agent", workflow_goal: ctx.goal },
|
||||
},
|
||||
{
|
||||
signal_id: "engagement.features_used",
|
||||
present: true,
|
||||
score: 88,
|
||||
raw: { source: "grow", workflow_goal: ctx.goal },
|
||||
raw: { source: "grow-agent", workflow_goal: ctx.goal },
|
||||
},
|
||||
{
|
||||
signal_id: "goals.goal_clarity",
|
||||
present: true,
|
||||
score: 90,
|
||||
raw: { source: "grow", workflow_goal: ctx.goal },
|
||||
raw: { source: "grow-agent", workflow_goal: ctx.goal },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -235,7 +235,7 @@ async function runQScoreCheck(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 runQScoreCheck(ctx: ServiceAgentContext): Promise<ServiceAgentRes
|
||||
);
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Q Score estimated ~${avgSignalScore} (service compute unavailable: formula store may not be seeded). Based on ${signals.length} signals.`,
|
||||
summary: `Quinn estimated Q-Score ~${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 runQScoreCheck(ctx: ServiceAgentContext): Promise<ServiceAgentRes
|
||||
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Q Score computed ${compute.q_score ?? "(unknown)"} for ${ctx.goal}.`,
|
||||
summary: `Quinn computed Q-Score ${compute.q_score ?? "(unknown)"} for ${ctx.goal}.`,
|
||||
detail: { ingest, compute, qscore_user_id: qscoreUserId },
|
||||
};
|
||||
}
|
||||
|
||||
// ── Resume Building (resume-builder service from growqr-app) ──
|
||||
// ── Resume Agent (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 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.",
|
||||
? `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.",
|
||||
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 Building unavailable: ${err instanceof Error ? err.message : String(err)}`,
|
||||
summary: `Resume Agent 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 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.`,
|
||||
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.`,
|
||||
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 runInterviewPractice(ctx)
|
||||
: healthCheck(config.interviewServiceUrl, "Mock Interview / interview-service");
|
||||
? await runSaraInterview(ctx)
|
||||
: healthCheck(config.interviewServiceUrl, "Sara / interview-service");
|
||||
case "roleplay-service":
|
||||
return ctx
|
||||
? await runRoleplayPractice(ctx)
|
||||
: healthCheck(config.roleplayServiceUrl, "Mock Roleplay / roleplay-service");
|
||||
? await runEmilyRoleplay(ctx)
|
||||
: healthCheck(config.roleplayServiceUrl, "Emily / roleplay-service");
|
||||
case "qscore-service":
|
||||
return ctx
|
||||
? await runQScoreCheck(ctx)
|
||||
: healthCheck(config.qscoreServiceUrl, "Q Score / qscore-service");
|
||||
? await runQuinnQScore(ctx)
|
||||
: healthCheck(config.qscoreServiceUrl, "Quinn / qscore-service");
|
||||
case "resume-service":
|
||||
return ctx
|
||||
? await runResumeTailor(ctx)
|
||||
: healthCheck(config.resumeServiceUrl, "Resume Building / resume-service");
|
||||
: healthCheck(config.resumeServiceUrl, "Resume Agent / resume-service");
|
||||
case "matchmaking-service":
|
||||
return ctx
|
||||
? await runMatchmaking(ctx)
|
||||
|
||||
Reference in New Issue
Block a user