Files
docs/growqrApp.md
-Puter e6685203fe feat: initial docs repo with project inventory and all documentation
- Added REPO_INVENTORY.md with all repos, branches, remotes, and staging info
- Added .gitignore
- Synced all existing docs from local workspace
- Centralized documentation hub for GrowQR team
2026-06-22 15:04:27 +05:30

23 KiB
Raw Permalink Blame History

Yeah — this repo is doing something pretty unusual, and honestly pretty smart.

The shortest possible explanation

This is not a normal “frontend ↔ API” app.

Its more like:

  • the UI is a Next.js app
  • the orchestrator is a super-agent router / session broker
  • the user-service is the identity + profile source of truth
  • the domain services are treated like agents
  • Redis Streams is the internal task bus
  • Q-Score is a cross-cutting scoring engine fed by events extracted from agent outputs
  • some flows are agentized, some are still direct REST, and live audio flows are direct WebSocket to specialty services

So the system has multiple communication lanes, not one.


1. The mental model: 4 communication lanes

Lane A — Direct frontend → service REST

Used for normal CRUD.

Examples:

  • frontend → user-service for /users/me, /users/ensure, photo upload, QR
  • frontend → resume-builder for resumes, versions, export, parse
  • frontend → social-branding for profiles, analysis history, content generation
  • frontend → interview-service / roleplay-service artifact APIs

This is the boring, standard lane.


Lane B — Frontend → orchestrator WebSocket → domain agents

Used for page sessions, agent actions, cross-service flows, progressive updates.

Examples:

  • home dashboard
  • resume hub/editor actions
  • social media page
  • job matching
  • pathways
  • some interview/roleplay setup/feedback flows

This is the signature architecture of the app.


Lane C — Domain agent ↔ orchestrator via Redis Streams or A2A HTTP

This is internal service-to-service orchestration.

The orchestrator can invoke an agent in two ways:

Default: Redis Streams

  • orchestrator writes task to tasks:<agent-name>
  • service worker consumes it
  • service publishes response messages to responses:<task_id>
  • orchestrator relays those messages back to frontend

Fallback: HTTP A2A

  • orchestrator POSTs to /<service>/a2a/tasks
  • service runs the same session logic and returns collected messages

So they built a dual transport layer:

  • event-driven / stream-based
  • with HTTP fallback

Thats very distinctive.


Lane D — Direct live media WebSockets

Used for real-time interview / roleplay audio sessions.

Examples:

  • frontend useInterviewSession() connects directly to interview-service
  • frontend useRoleplaySession() connects directly to roleplay-service

So live voice does not go through the orchestrator data path.

Thats another important part of the uniqueness:
not everything is forced through one gateway.


2. UI app: how the frontend is structured

Main app: frontend/

Core stack:

  • Next.js 16
  • React 19
  • Clerk auth
  • App Router
  • Tailwind
  • custom hooks for API + agent sessions

2.1 Frontend has two personalities

The UI is doing both:

A. Standard SaaS frontend

Using API clients like:

  • src/lib/api/users.ts
  • src/lib/api/resumes.ts
  • src/lib/api/socialBranding.ts

These attach Clerk Bearer tokens and call REST endpoints directly.

B. Agent-driven frontend

Using:

  • src/hooks/useAgentSession.ts

This hook is the key.

It:

  • gets Clerk token
  • opens WebSocket to orchestrator:
    ws://.../ws/agent?token=<clerk_jwt>
  • sends:
    { "type": "session_start", "page": "...", "params": {...} }
    
  • later sends:
    { "type": "user_action", "action": "...", "params": {...} }
    

And it listens for structured messages like:

  • capabilities_updated
  • agent_routed
  • agent_thinking
  • agent_data
  • agent_suggestion
  • agent_activity
  • agent_error
  • qscore_loaded
  • qscore_updated
  • copilot streaming chunks

So the frontend is basically talking to a message protocol, not just fetching JSON.


2.2 Important frontend providers

QScoreProvider

At dashboard layout level:

  • frontend/src/app/(dashboard)/layout.tsx

This persists Q-Score across dashboard navigation.

UserProvider

In DashboardLayout.tsx

This does:

  • usersApi.ensureUser()
  • loads the GrowQR user record
  • exposes user, isPro, refreshUser

So Clerk is auth, but user-service is the app identity profile.


2.3 What page loads feel like

When a page uses useAgentSession(...), the frontend is not directly loading page data from the domain service.

Instead:

  1. connect to orchestrator
  2. orchestrator authenticates
  3. orchestrator resolves user context via user-service
  4. orchestrator routes page to correct agent
  5. agent sends structured messages back
  6. UI renders from latestData[action]

Example:

  • Home page uses: useAgentSession(WS_URL, "home")
  • It waits for dashboard_loaded

So the page isnt “call GET /dashboard”. Its more like:

“Start an agent session for page home and stream me the result.”

Thats very different from normal SaaS frontend architecture.


2.4 Frontend communication split by feature

Home dashboard

  • goes through orchestrator
  • page = "home"
  • routed to dashboard-service

Resume pages

  • direct REST for CRUD/export/parse
  • orchestrator WS for page session + AI/copilot/page-level orchestration

Social branding

  • direct REST for profiles/analysis/content
  • orchestrator WS for page session and agent-like flows

Settings / profile

  • direct REST to user-service
  • user data also available through orchestrator-resolved context

Job matching

  • orchestrator-driven agent page

Pathways

  • orchestrator-driven agent page

Interview / roleplay

  • setup/feedback pages use orchestrator-style session hooks
  • actual live conversation uses direct WS to the interview/roleplay service

Helper chat

Theres also:

  • frontend/src/app/api/chat/route.ts

That one calls OpenAI directly from Next server route, outside the orchestrator pattern.

So even inside frontend, youve got:

  • direct REST
  • orchestrated WS
  • direct live WS
  • direct Next server AI route

3. Orchestrator: what it really is

Main folder:

  • orchestrator/

Key file:

  • orchestrator/app/main.py
  • orchestrator/app/agent/session.py

The orchestrator is basically:

a WebSocket gateway

  • auth layer
  • service discovery layer
  • routing layer
  • context enrichment layer
  • signal extraction layer
  • Q-Score event publisher
  • background workflow coordinator

Thats why it feels unique.


3.1 What happens when frontend connects

In orchestrator/app/agent/websocket.py:

  1. WebSocket opens with ?token=<clerk_jwt>
  2. orchestrator verifies Clerk JWT
  3. extracts Clerk user id
  4. creates SuperAgentSession

Then in SuperAgentSession.run():

Step 1

Send capabilities snapshot

Step 2

Resolve user identity via user-service

Step 3

Process:

  • session_start
  • user_action
  • session_end

So every agent session begins with:

  • who is this user?
  • what agents are available?
  • whats the current user context?

3.2 Service discovery is dynamic

Files:

  • orchestrator/app/discovery/registry.py
  • orchestrator/app/discovery/health.py

The orchestrator periodically calls:

  • /.well-known/agent-card.json
  • fallback /.well-known/agent.json

from each configured agent URL.

It stores:

  • agent name
  • description
  • skills
  • health state

So the orchestrator is not hardcoded to just “known APIs”.
It has a registry of discovered agent capabilities.

Thats a real agent-style pattern.


3.3 Routing is page-based and action-based

In orchestrator/app/agent/session.py:

Page routing

PAGE_TO_AGENT maps:

  • homedashboard-service
  • resume-hubresume-builder
  • social-mediasocial-branding
  • job-matchingmatchmaking-service
  • settingsuser-service
  • pathwayspathways-service
  • etc.

Action routing

ACTION_TO_AGENT maps actions like:

  • ai_analyzeresume-builder
  • run_analysissocial-branding
  • update_preferencesuser-service
  • get_feedmatchmaking-service
  • generate_pathwaypathways-service

So the orchestrator understands:

  • “which page belongs to which agent”
  • “which user action belongs to which agent”

That makes the frontend simpler: it just emits actions, not service URLs.


3.4 The orchestrator always resolves user context first

This is one of the most important design choices.

In _resolve_user_identity():

  1. call user-service action view_profile
  2. get the real user profile
  3. extract the app UUID
  4. enrich with skills from:
    • resume-builder /api/state/{clerk_id}
    • social-branding /api/state/{clerk_id}

So the orchestrator creates a portable user context that can be sent to any domain agent.

That means agents dont each need to re-fetch identity basics every time.

This is a huge reason the architecture feels “agentic” instead of “API-ish”.


3.5 Dual transport: Redis first, HTTP fallback

Files:

  • orchestrator/app/a2a/redis_transport.py
  • orchestrator/app/a2a/client.py

Redis mode

  • publish task to tasks:<agent>
  • subscribe to responses:<task_id>
  • stream messages to frontend in real time

HTTP mode

  • POST to /<agent>/a2a/tasks
  • get collected messages back

So the orchestrator can treat agents as:

  • synchronous HTTP responders
  • or event-driven streaming workers

Thats sophisticated.


3.6 Q-Score is deeply embedded into orchestration

This is another unique thing.

The orchestrator doesnt just route actions.
It also watches agent outputs and turns them into scoring signals.

Files:

  • orchestrator/app/qscore/signal_mapper.py
  • publisher.py
  • subscriber.py
  • client.py

On session init

It:

  • fetches latest Q-Score over HTTP from qscore-service
  • registers callback for live score updates via Redis stream
  • publishes engagement + onboarding signals

After domain-agent responses

It scans returned messages:

  • resume analysis
  • LinkedIn analysis
  • job matching actions
  • interview review
  • roleplay review
  • pathway progress

Then extracts signals like:

  • resume.ats_compatibility
  • linkedin.headline_quality
  • matching.feed_active
  • interview.overall_score
  • pathway.completion_pct

Then publishes them to:

  • qscore.signal_events

So the orchestrator is not just a router.
It is also a semantic event interpreter.

Thats rare.


3.7 Background workflows are coordinated here

Endpoint:

  • POST /api/v1/background-task

Used by:

  • resume-builder after resume upload
  • social-branding after LinkedIn fetch/connect

Flow:

  1. service notifies orchestrator
  2. orchestrator resolves user context
  3. dispatches background task to agent
  4. collects messages
  5. extracts Q-Score signals
  6. extracts auto-populate info
  7. PATCHes user-service /users/auto-populate

So the orchestrator also acts like a workflow engine.


4. User-service: why its more important than it first looks

Main folder:

  • user-service/

Key files:

  • app/main.py
  • app/api/v1/users.py
  • app/api/v1/webhooks.py
  • app/agent/session.py

This service is not just “users table CRUD”.

It is the identity gateway of the whole platform.


4.1 It is the source of truth for app-level user state

It owns:

  • GrowQR user record
  • Clerk id mapping
  • email / first_name / last_name
  • avatar_url
  • plan
  • preferences
  • metadata
  • QR code data
  • profile photo
  • onboarding state (stored in preferences)

This is the canonical application profile.


4.2 It bridges Clerk identity to internal GrowQR identity

Frontend auth is Clerk.

But Q-Score and some internal flows need a stable internal user UUID.

That comes from user-service.

So the mapping is basically:

  • Clerk user id → auth identity
  • GrowQR user UUID → internal cross-service identity

Thats why orchestrator hits user-service first.


4.3 It supports three access styles

1. Direct REST from frontend

Examples:

  • /api/v1/users/me
  • /api/v1/users/ensure
  • /api/v1/users/me/photo
  • /api/v1/users/me/qr-code

2. Agent-style access

Via:

  • /ws/agent
  • /a2a/tasks

So user-service itself behaves like an agent.

3. Internal state endpoint

  • /api/state/{clerk_id}

Used by dashboard-service and orchestrator.

So user-service exposes both:

  • user-facing API
  • internal aggregation API
  • agent protocol surface

4.4 It auto-creates users on first sign-in

In UserProvider, frontend calls:

  • ensureUser()

Server side /users/ensure:

  • if user exists → return it
  • if not → fetch from Clerk API if possible
  • create GrowQR user row
  • generate QR code

So app account creation is lazy and synchronized off Clerk login.


4.5 It stores onboarding state

The frontend hook:

  • useOnboarding()

writes onboarding into:

  • user.preferences.onboarding

This is clever because onboarding becomes part of the shared user context, not some isolated frontend state.

That means orchestrator and Q-Score can use it.


4.6 It handles Clerk webhooks

In app/api/v1/webhooks.py:

Supported:

  • user.created
  • user.updated
  • user.deleted
  • session.created

Effects:

  • create/update/soft-delete user
  • update last sign-in
  • trigger downstream cleanup on delete

So user-service is the sync point between external auth identity and internal domain data.


4.7 It also owns QR/public identity features

It handles:

  • uploaded profile photo
  • branded QR generation
  • public profile endpoint
  • QR regeneration

Thats not just admin/user metadata. It also powers the outward-facing GrowQR identity layer.


4.8 Auto-populate is a hidden important feature

Orchestrator can call:

  • PATCH /api/v1/users/auto-populate

It fills empty user/profile/settings fields based on:

  • parsed resume data
  • LinkedIn profile data

But only if those fields are empty.

That creates a very smooth onboarding loop:

  • upload resume / connect LinkedIn
  • AI/background processing runs
  • settings fill themselves

This is one of the better-designed pieces.


5. Why this architecture feels unique

Because the services arent just APIs.

They are treated like page/domain agents with a common invocation model.

The pattern is:

Each service has a central session object, like:

  • UserAgentSession
  • DashboardAgentSession
  • similar in resume/social/etc.

That same session logic can be invoked through:

A. native WebSocket

actual user-agent page session

B. HTTP A2A

via /a2a/tasks

C. Redis Stream worker

consumes tasks:<agent> and publishes responses

That means one domain behavior implementation is reused across multiple transports.

Thats actually a strong architectural idea.


6. How each service communicates with others

Heres the clean map.

6.1 Frontend

Talks to:

  • user-service via REST
  • resume-builder via REST
  • social-branding via REST
  • orchestrator via WS
  • interview-service via direct WS + REST
  • roleplay-service via direct WS + REST
  • internal Next /api/chat route for helper chat

6.2 Orchestrator

Talks to:

  • user-service for identity resolution + metadata persistence + auto-populate target
  • resume-builder for page/actions + skill enrichment + background parse/analyze
  • social-branding for page/actions + skill enrichment + background analysis
  • dashboard-service for home page payload
  • matchmaking-service for job matching actions
  • pathways-service for pathway actions
  • qscore-service for initial read + signal publication + update subscription

Transport used:

  • Redis Streams preferred
  • HTTP A2A fallback
  • HTTP internal state calls for enrichment

6.3 Dashboard-service

Talks to:

  • user-service /api/state/{clerk_id}
  • resume-builder /api/state/{clerk_id}
  • social-branding /api/state/{clerk_id}
  • qscore-service /v1/qscore/{uuid}

It acts like an aggregator service:

  • collect distributed state
  • merge
  • evaluate card rules
  • compute lifecycle
  • return dashboard_loaded

So dashboard-service is basically a read model / composition engine.


6.4 Resume-builder

Talks to:

  • orchestrator for background-task kickoff after upload
  • likely user auth verification through Clerk token on direct REST
  • exposes state to orchestrator/dashboard
  • emits analysis data consumed by orchestrator → qscore

It is both:

  • CRUD/document service
  • AI analysis agent

6.5 Social-branding

Talks to:

  • orchestrator for background-task kickoff after LinkedIn profile connect/fetch
  • exposes state to orchestrator/dashboard
  • emits analysis data that becomes qscore signals

It is both:

  • profile ingestion service
  • AI profile-analysis agent

6.6 User-service

Talks to:

  • Clerk webhooks
  • resume/social cleanup endpoints on delete
  • orchestrator
  • dashboard-service
  • frontend

It is the central identity/state hub.


6.7 Qscore-service

Talks to:

  • orchestrator via HTTP read + Redis streams
  • receives signals on qscore.signal_events
  • computes score
  • publishes updates on qscore.score_updates

So Qscore is event-fed, not directly computed inline on every user request.

Thats a strong decoupling.


6.8 Matchmaking-service

Observed pattern:

  • external sibling service
  • has Redis stream worker + A2A endpoints
  • orchestrator routes job-matching actions to it
  • likely handles scraping, ranking, worker queues

So it fits the agentized service model.


6.9 Pathways-service

Observed pattern:

  • external sibling service
  • Redis worker + A2A endpoints
  • has /api/state/{clerk_id}
  • orchestrator routes pathway actions to it

Also fits the agentized pattern.


6.10 Interview-service / Roleplay-service

These are more hybrid.

Definitely observed:

  • direct REST configure/review endpoints
  • direct live WebSocket session endpoints
  • frontend hooks connect directly for live audio

Architectural meaning:

These are specialized real-time media services. They dont cleanly fit the normal agent/session transport path the way user/resume/social/dashboard/pathways/matchmaking do.

So the system is not “pure agent architecture”. It is agent architecture + specialized realtime sidecars.

That hybrid is part of what makes it interesting.


7. End-to-end flows

Flow A — User opens Home

  1. frontend connects to orchestrator WS
  2. orchestrator verifies Clerk JWT
  3. orchestrator asks user-service for profile
  4. orchestrator enriches user context from resume/social state
  5. orchestrator fetches initial Q-Score
  6. frontend sends session_start(page="home")
  7. orchestrator routes to dashboard-service
  8. dashboard-service fetches state from user/resume/social/qscore
  9. dashboard-service builds dashboard_loaded
  10. orchestrator relays it to frontend
  11. later Q-Score updates can stream in live

Thats a very nontraditional home-page load.


Flow B — User changes settings

  1. frontend calls user-service /users/me directly
  2. user-service updates preferences
  3. those preferences become part of future orchestrator-resolved context
  4. Q-Score / pathways / matching can later use them

So settings are not isolated—they feed the broader system.


Flow C — User uploads resume during onboarding

  1. frontend uploads PDF directly to resume-builder
  2. resume-builder stores PDF + thumbnail
  3. resume-builder triggers orchestrator /api/v1/background-task
  4. orchestrator resolves user context
  5. orchestrator dispatches bg_parse_analyze
  6. resume-builder parses PDF, builds structured content, runs AI analysis
  7. orchestrator extracts:
    • auto-populate fields
    • qscore signals
  8. orchestrator PATCHes user-service /users/auto-populate
  9. user settings/profile become prefilled

Thats a really elegant async flow.


Flow D — User connects LinkedIn

  1. frontend hits social-branding
  2. social-branding stores/fetches profile
  3. social-branding triggers orchestrator background task
  4. orchestrator dispatches bg_analyze
  5. social-branding emits profile_loaded, profile_scored, analysis_complete
  6. orchestrator:
    • extracts auto-populate fields
    • publishes qscore signals
    • updates user-service

Again: domain output becomes shared platform intelligence.


Flow E — User starts interview live session

  1. frontend likely uses orchestrator/setup flow to configure session metadata
  2. interview-service returns session_id
  3. frontend opens direct WS to interview-service
  4. audio packets stream directly
  5. review/artifacts are later fetched from interview-service
  6. orchestrator may later use review/history outputs for Q-Score

So live media bypasses the general agent path for latency reasons.


8. What makes this architecture strong

Strengths

1. Clear separation of concerns

  • UI
  • identity
  • orchestration
  • domain intelligence
  • scoring
  • realtime media

2. Reusable agent/session abstraction

Same business session logic can work over:

  • frontend WS
  • HTTP A2A
  • Redis worker

3. Great for incremental AI adoption

AI doesnt have to be one giant “assistant”. Each domain agent is specialized.

4. Cross-service intelligence

Resume + social + onboarding + engagement all feed Q-Score.

5. Good async workflow design

Background tasks avoid blocking user flows.

6. Nice read-model approach on dashboard

Dashboard-service composes distributed state into one home payload.


9. Tradeoffs / complexity

This architecture is powerful, but expensive mentally.

1. Multiple protocols

You have:

  • REST
  • orchestrator WS
  • Redis streams
  • A2A HTTP
  • direct media WS

Thats a lot to hold in your head.

2. Mixed patterns

Some services are fully agentized. Some are direct APIs. Some are hybrid realtime.

3. Debugging is multi-hop

A page issue might involve:

  • Clerk token
  • orchestrator auth
  • user-service resolution
  • agent routing
  • Redis stream worker
  • qscore side effects

4. Service discovery + routing adds indirection

Great power, but more moving parts.

5. Some services live outside repo root

Operationally a bit awkward.


10. The cleanest way to describe this app

If I had to describe GrowQR to an engineer in one paragraph:

GrowQR is a hybrid microservice platform where the frontend uses both direct REST and a central orchestrator WebSocket. The orchestrator authenticates users once, resolves a shared user context through user-service, dynamically discovers domain agents, routes page sessions and actions to those agents over Redis Streams or HTTP A2A, extracts semantic signals from their outputs, and feeds those signals into a separate Q-Score engine. Meanwhile, traditional CRUD and low-latency media workflows still use direct service APIs and direct WebSockets. So the system combines SaaS API architecture, agent orchestration, event-driven messaging, and realtime media pipelines in one platform.

Thats why it feels unique.


11. The single most important insight

The user-service + orchestrator combo is the heart.

  • user-service = “who is this user in GrowQR?”
  • orchestrator = “given this user and this page/action, which domain intelligence should run now?”

Everything else plugs into that.


If you want, next I can do one of these:

  1. draw you a plain-English sequence diagram for the whole app
  2. map every page in frontend to its backend path/service
  3. explain only the orchestrator internals deeply
  4. make you an architecture doc you can share with your team