graph TB
%% ═══ USER LAYER ═══
subgraph USERS["🧑 Users"]
CLI["CLI / python -m src.harness.run_cycle"]
WEB["Static Dashboard<br/>dashboard.html + data.js"]
AGENT["Agent Harness<br/>python -m src.harness (SDK tools)"]
end
%% ═══ HARNESSES ═══
subgraph HARNESS["⚙️ Scouting Cycle Harness<br/>src/harness/run_cycle.py"]
PIPELINE["run_scouting_cycle()<br/>(7-step pipeline)"]
PKG["package_output()<br/>(render + export)"]
CLI_MAIN["main() — argparse CLI<br/>--list-seen / --resume --query"]
end
%% ═══ CORE MODULES ═══
subgraph RESUME_PARSING["📄 Resume Parsing"]
RP_PARSE["parse_resume() → ParsedResume"]
RP_FORMATS["PDF (pypdf) / DOCX (python-docx) / .txt"]
RP_MODEL["ParsedResume<br/>pydantic model"]
end
subgraph JOB_SEARCHING["🔍 Job Search"]
JS_SEARCH["search_jobs.handler() → List[JobResult]"]
LS_LINKEDIN["linkedin_search.py<br/>parse.bot API"]
JD_RESULT["JobResult / JobDetail<br/>pydantic models"]
end
subgraph MATCHING_SCORING["🎯 Matching & Scoring"]
SCORER["match_and_score() → MatchResult"]
SCORE_FORMULA["0.6 × cosine_sim + 0.4 × skill_overlap"]
FILTERER["filter_top_matches()"]
MR_RESULT["MatchResult<br/>score, matched_skills, missing_skills, rationale"]
end
subgraph CONTENT_GENERATION["📝 Content Generation"]
RT_TAILOR["tailor_resume() → TailoredResume"]
CLW_WRITE["write_cover_letter() → CoverLetter"]
DR_RENDER["doc_renderer.py<br/>render_*_docx / render_*_pdf"]
end
subgraph PERSISTENCE["💾 Persistence"]
DB_MGR["DatabaseManager<br/>(SQLite seen_jobs / apps)"]
EXCEL_TRACKER["tracker.xlsx<br/>openpyxl output"]
FOLDERS["applications/<br/>per-job folders"]
end
subgraph LOCAL_SERVICES["🖥️ Local Services"]
OLLAMA["Ollama<br/>nomic-embed-text (768-dim)"]
CHROMA_DB["(ChromaDB<br/>data/chroma_db/)"]
end
subgraph CLOUD_SERVICES["☁️ Cloud Services"]
LLM_API["OpenAI-compatible LLM API<br/>Claude / GPT via MODEL_NAME + API_URL"]
PARSE_BOT["(parse.bot API<br/>LinkedIn scraper)"]
end
subgraph TRACING["📊 Observability"]
LANGFUSE["Langfuse (optional)<br/>CycleTrace + spans"]
end
%% ═══ FLOWS ═══
CLI --> PIPELINE
AGENT --> PIPELINE
WEB --> EXCEL_TRACKER
PIPELINE --> RP_PARSE
RP_PARSE --> RP_MODEL
PIPELINE --> JS_SEARCH
JS_SEARCH --> LS_LINKEDIN
LS_LINKEDIN --> PARSE_BOT
PIPELINE --> SCORER
SCORER --> SCORE_FORMULA
SCORER --> CHROMA_DB
SCORER --> OLLAMA
SCORE_FORMULA --> MR_RESULT
PIPELINE --> RT_TAILOR
PIPELINE --> CLW_WRITE
RT_TAILOR --> LLM_API
CLW_WRITE --> LLM_API
PIPELINE --> PKG
PKG --> DR_RENDER
DR_RENDER --> FOLDERS
PKG --> EXCEL_TRACKER
PIPELINE --> DB_MGR
PIPELINE -. trace .-> LANGFUSE
SCORER -. trace .-> LANGFUSE
RT_TAILOR -. trace .-> LANGFUSE
CLW_WRITE -. trace .-> LANGFUSE
CLI_MAIN --> PIPELINE
CLI_MAIN --> DB_MGR
style USERS fill:#e8f4fd,stroke:#333,stroke-width:2px
style HARNESS fill:#fff3cd,stroke:#d4a017,stroke-width:2px
style RESUME_PARSING fill:#d1ecf1,stroke:#333,stroke-width:1px
style JOB_SEARCHING fill:#e2d5f1,stroke:#333,stroke-width:1px
style MATCHING_SCORING fill:#d4edda,stroke:#28a745,stroke-width:2px
style CONTENT_GENERATION fill:#fce4ec,stroke:#333,stroke-width:1px
style PERSISTENCE fill:#f8d7da,stroke:#dc3545,stroke-width:1px
style LOCAL_SERVICES fill:#e8f5e9,stroke:#2c7be5,stroke-width:2px
style CLOUD_SERVICES fill:#fff3cd,stroke:#ffc107,stroke-width:2px
style TRACING fill:#f3e5f5,stroke:#6f42c1,stroke-width:1px
graph TB
subgraph LOCAL["🖥️ Local Machine"]
ENV["Python ≥ 3.11 / venv<br/>pip install -e .[dev]"]
PY_APP["JobScout Application"]
subgraph OLLAMA_SVC["Ollama (local, port 11434)"]
MODEL_EMB["nomic-embed-text<br/>(~768-dim)"]
end
CHROMA["(ChromaDB DB<br/>data/chroma_db/ on disk)"]
subgraph LANGFUSE_OPT["Langfuse (optional)"]
LCLOUD[cloud.langfuse.com or self-hosted]
end
end
subgraph CLOUD["☁️ External APIs"]
LLM_API["OpenAI-compatible LLM API (Claude, GPT-4, etc.)"]
PARSE_BOT[parse.bot Scraper API]
end
PY_APP --> OLLAMA_SVC
PY_APP --> CHROMA
PY_APP --> LANGFUSE_OPT
PY_APP --> LLM_API
PY_APP --> PARSE_BOT
style LOCAL fill:#f0f0f0,stroke:#333,stroke-width:2px
style OLLAMA_SVC fill:#e8f5e9,stroke:#4caf50
style CHROMA fill:#fce4ec,stroke:#e91e63
style LANGFUSE_OPT fill:#f3e5f5,stroke:#9c27b0
style CLOUD fill:#fff3cd,stroke:#ffc107
flowchart LR
subgraph AGENT_HARNESS["src/harness/__main__.py (Agent)"]
SDK_DISC["Discover __sdk_tools__<br/>from each tool module"]
MCP_SVR["MCP Server<br/>(tool endpoints)"]
INTERACT["Interactive Agent Loop<br/>(auto-approved permissions)"]
end
subgraph TOOL_MODULES["Tool Modules (each exposes __sdk_tools__)"]
RP_TOOL["resume_parse tool<br/>from resume_parser.py"]
SCORER_TOOL["match_and_score tool<br/>from scorer.py"]
JS_TOOL["search_jobs tool<br/>from job_search.py"]
end
AGENT_HARNESS --> SDK_DISC
SDK_DISC --> MCP_SVR
MCP_SVR --> INTERACT
RP_TOOL -. exported via __sdk_tools__ .-> SDK_DISC
SCORER_TOOL -. exported via __sdk_tools__ .-> SDK_DISC
JS_TOOL -. exported via __sdk_tools__ .-> SDK_DISC
style AGENT_HARNESS fill:#e8f4fd,stroke:#2c7be5,stroke-width:2px
style TOOL_MODULES fill:#d1ecf1,stroke:#333
flowchart LR
subgraph ARG["CLI Args"]
RESUM[--resume path/to/file]
QUERY[--query senior designer]
LOC[--location NYC]
LIM[--limit 10]
THR[--threshold 70]
MAX[--max-tailored 5]
PROV["--provider ollama|anthropic"]
end
ARG --> PIPELINE
subgraph PIP["Pipeline Stages (run_scouting_cycle)"]
S1(["Step 1: parse_resume"])
S2(["Step 2: search_jobs"])
S3(["Step 3: dedup via DBManager"])
S4(["Step 4: match_and_score × N"])
S5(["Step 5: filter_top_matches"])
S6A(["Step 6a: tailor_resume"])
S6B(["Step 6b: write_cover_letter"])
S6C(["Step 6c: cap check"])
S7(["Step 7: package_output"])
end
PIPELINE --> S1
PIPELINE --> S2
S2 --> S3
S3 --> S4
S4 --> S5
S5 --> S6C{"score ≥ threshold?"}
S6C --> S6A
S6C --> SKIP["skip job"]
S6A --> S6B
S6A --> S6C2{"tailor < max_tailored?"}
S6C2 --> S6A
S6C2 --> CAP_OUT["cap out — skip tailoring"]
S6B --> S7
S1 --> RP_MODEL["(ParsedResume)"]
S4 --> SCORER_MODEL["(MatchResult)"]
S6A --> RT_MODEL["(TailoredResume)"]
S6B --> CLW_MODEL["(CoverLetter)"]
S7 --> PKG_RESULT["(CycleResult × N)"]
style PIPELINE fill:#fff3cd,stroke:#d4a017,stroke-width:2px
style S4 fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style S5 fill:#d4edda,stroke:#28a745,stroke-width:2px
style S6A fill:#e8f4fd,stroke:#007bff,stroke-width:2px
style S7 fill:#f8d7da,stroke:#dc3545,stroke-width:2px