Files
matchmaking-v2/app/agent/session.py
raulgupta 89ca9ad647 Initial commit: matchmaking-v2 on-demand multi-board job-search agent
On-demand Scout service (replaces the nightly aggregator):
- FastAPI agent-mesh service; card name "matchmaking-service" (HTTP /a2a/tasks
  + Redis-stream worker matching the sibling-service pattern)
- run_search: parallel multi-board sweep (Naukri/blackfalcondata + Foundit +
  LinkedIn), city-filtered at the board, cheapest-per-result first
- per-board adapters + normalizers -> ScoutJob with rich read-only details and
  offsite apply links; recall mode + MVQ guard
- result cache for dev replay ($0); per-board cost knobs; retry-on-5xx
- research/: engine design, board cost economics, India-actor shortlist, POC
2026-06-18 19:22:03 +05:30

92 lines
4.2 KiB
Python

"""Matchmaking-v2 agent session.
Mirrors the mesh contract: the orchestrator calls /a2a/tasks, which drives
on_session_start / on_user_action; handlers `push` messages back (agent_thinking,
agent_data{action,data}, agent_error) that the orchestrator forwards to the frontend.
Step-0 scaffold: the dispatch + contract are wired; handlers are honest stubs that
say "not implemented yet" so we can fill them in one full-stack slice at a time.
"""
from __future__ import annotations
import logging
logger = logging.getLogger(__name__)
class MatchmakingAgentSession:
def __init__(self, websocket, user_id: str):
self.ws = websocket
self.user_id = user_id
self.context: dict = {}
async def push(self, msg_type: str, **kwargs):
await self.ws.send_json({"type": msg_type, **kwargs})
async def _stub(self, action: str, note: str):
await self.push("agent_data", action=action, data={"status": "not_implemented", "note": note})
async def on_session_start(self, params: dict):
self.context["user_context"] = params.get("user_context")
await self.push("agent_thinking", message="Scout is warming up…")
# Real flow lands in a later slice: resolve prefs → run_search → emit feed.
await self.push("agent_data", action="session_ready", data={"service": "matchmaking-v2", "version": "2.0.0"})
async def on_user_action(self, action: str, params: dict):
handlers = {
# existing contract
"sync_preferences": self.handle_sync_preferences,
"get_feed": self.handle_get_feed,
"record_feedback": self.handle_record_feedback,
"get_opportunity_detail": self.handle_get_opportunity_detail,
# new in v2
"run_search": self.handle_run_search,
"tailor_resume": self.handle_tailor_resume,
"submit_application": self.handle_submit_application,
"get_apply_proof": self.handle_get_apply_proof,
}
handler = handlers.get(action)
if not handler:
await self.push("agent_error", message=f"Unknown action: {action}")
return
try:
await handler(params)
except Exception as e: # noqa: BLE001 — surface to the client, log the trace
logger.error("action '%s' failed: %s", action, e, exc_info=True)
await self.push("agent_error", message=f"Failed to {action}: {e}")
# ── handlers (stubs — replaced one full-stack slice at a time) ──
async def handle_sync_preferences(self, params: dict):
await self._stub("preferences_synced", "persist ScoutPrefs → label store")
async def handle_get_feed(self, params: dict):
await self._stub("feed_loaded", "return cached ranked feed")
async def handle_record_feedback(self, params: dict):
await self._stub("feedback_recorded", "store SAVE/DISMISS/APPLY label")
async def handle_get_opportunity_detail(self, params: dict):
await self._stub("opportunity_detail", "return match-score breakdown")
async def handle_run_search(self, params: dict):
"""Slice-1a: ScoutPrefs → live multi-board fetch → cards. No engine, no user-arm."""
from app.engine import search
prefs = params # the frontend's ScoutPrefs arrive as params (user_context, if present, is ignored)
if not search.has_mvq(prefs):
await self.push("agent_data", action="search_complete",
data={"opportunities": [], "needs": ["title", "location"]})
return
await self.push("agent_thinking", message="Scanning live roles across job boards…")
result = await search.run_sweep(prefs)
await self.push("agent_data", action="search_complete", data=result)
async def handle_tailor_resume(self, params: dict):
await self._stub("resume_tailored", "resume-builder: tailor resume to the chosen role")
async def handle_submit_application(self, params: dict):
await self._stub("application_submitted", "master-key auto-apply (tier-aware)")
async def handle_get_apply_proof(self, params: dict):
await self._stub("apply_proof", "return captured confirmation screenshots")