Releases: senthilsweb/agent-job-matcher
Release list
v0.9.0
v0.9.0 (2026-07-13)
This release is published under the MIT License.
Features
- chat: Emit reasoning in tags for the client's thinking panel (
0c25fa3)
One instruction added to the chat system prompt: start each reply with brief reasoning inside ..., keeping the visible answer outside the tags. The mcp-chat-client widget (as of its add-live-thinking-panel change) renders this as a Claude-style collapsible panel — reasoning streams into it live, then it collapses to "Thought for Xs" when the answer begins. Verified over a live /chat/stream turn: the model emits the tags split across SSE chunks and the panel handles them correctly.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.8.0...v0.9.0
v0.8.0
v0.8.0 (2026-07-12)
This release is published under the MIT License.
Features
- ci: Publish all four images to GHCR, fix chat-demo's browser-unreachable URL (
375c3c2)
build-and-publish.yml becomes a matrix building every image this repo produces — agent-job-matcher (backend/CLI, unchanged name), -agent-service, -playground, and -openapi-docs — so a production deployment can run entirely from pulled GHCR images with zero local builds. Per-image GHA cache scopes so the matrix legs don't evict each other's layers.
Also fixes a latent bug caught while building that production setup: chat-demo's AGENT_SERVICE_URL default was the compose-network hostname http://agent-service:6011, but that value lands in runtime-config.js and is consumed by the chat widget running client-side in the visitor's BROWSER (verified: every apiEndpoint consumer in mcp-chat-client/src/lib/api.ts is a browser fetch) — a compose-network name can never resolve there. Default is now the browser-reachable http://localhost:6011.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.7.1...v0.8.0
v0.7.1
v0.7.1 (2026-07-12)
This release is published under the MIT License.
Bug Fixes
- salary: Capture multi-location pay figures, drop the hardcoded $ icon (
6c36908)
Owner-tested against a real Bain posting: the extraction returned only one salary figure when the posting actually states two different location-tied amounts (a pattern several US states' pay-transparency laws require — one figure for Atlanta/Boston/Texas/Chicago, a different one for California/New York/DC/Washington). Root-caused by fetching the real page text: the original verbatim-only prompt rule had no instruction for this shape, so the model picked one figure rather than merging or fabricating.
analysis_system.txt now explicitly instructs capturing ALL location-tied figures with their location context instead of collapsing to one. Re-ran the exact same Bain URL to confirm: both figures now present.
Also fixed the UI's DollarSign icon, which hardcoded a $ glyph next to already-currency-prefixed text ("$ $293,500" — the owner's reported double-currency bug) and would have been misleading for a non-USD posting regardless. Swapped for lucide's currency-neutral Banknote icon, fixing both problems at once. The badge now truncates with the full text in a title attribute, since multi-location disclosures run much longer than a short range.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Detailed Changes: v0.7.0...v0.7.1
v0.7.0
v0.7.0 (2026-07-12)
This release is published under the MIT License.
Bug Fixes
- compose: Restart policy on the whole demo stack, ignore CLI run artifacts (
7870e6e)
Only api/agent-service had restart: unless-stopped; playground, openapi-docs, and chat-demo didn't, so a Docker Desktop restart brought back half the stack automatically and left the rest down with no indication why. All five services now self-heal consistently.
Also: runs/ (jobmatch analyze --out, docker compose run cli) was never gitignored — a real gap, surfaced when a local verification run left real output files showing as untracked.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
-
playground: Sharp corners, scoring guide, Clear button, salary display (
6957088) -
components/ui/button.tsx: every variant/size now rounded-none (was rounded-lg / per-size radius overrides) — sharp corners app-wide - nav-rail.tsx: new ScoringGuide fills the rail's previously-empty space — all 5 match bands with their exact score ranges (from scoring.py's match_band_for(), 80/65/50/35 boundaries) and colors matching lib/types.ts's MATCH_STATUS_CLASSES; nav item label shortened "Analyze fit" -> "Analyze" to match privacyshield's terse verb-only nav items (the fuller label stays on the page header and Card title, same split privacyshield itself uses) - sidebar-form.tsx + page.tsx: new Clear button resets the form's own state and, via a new onClear prop, the results/error state too - report-card.tsx + lib/types.ts: salary_range rendered as a pill in the collapsed accordion trigger, next to company name
Also root-caused the "Backend unreachable" status pill bug reported during UAT: not a code issue. Docker Desktop itself had degraded after several days of uptime across 23 containers — docker exec/restart started reporting success without the daemon taking effect, confirmed via container-to-container connection-refused despite docker inspect reporting healthy. Restarting Docker Desktop resolved every symptom immediately. Full details in openspec/changes/fix-playground-ui-parity Bolt 8.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Features
- schema: Add salary_range to JobAnalysis, extracted verbatim (
e818cc1)
Owner UAT observation: the playground's report cards had no salary info, and it wasn't clear whether that was a UI gap or missing from the schema entirely — traced to the latter. JobAnalysis gains salary_range: str | None, copied verbatim from the posting when stated, null when it isn't. Deliberately unstructured (not {min,max,currency,period}) — postings state pay in wildly inconsistent formats and forcing early structure risks misrepresenting a posting's actual wording. Not a scoring input; purely informational.
Verified with a real extraction, not assumed: docker compose run cli analyze against the Anthropic eval fixture (which states "$405,000 - $485,000") correctly extracted the range verbatim. Live postings without a stated range correctly returned null rather than a fabricated value. 111/111 offline tests pass (110 existing + 1 new).
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Detailed Changes: v0.6.0...v0.7.0
v0.6.0
v0.6.0 (2026-07-12)
This release is published under the MIT License.
Features
- compose: Containerize the MCP Bridge, put the whole stack on one port scheme (
b39560b)
mcp/agent-service/Dockerfile (new): the MCP server (mcp/index.js) has no network port of its own — it's a pure stdio bridge, spawned as a child process by the agent-service via pydantic-ai's StdioTransport. So there's no separate "MCP container": this one image bundles both Python (FastAPI/pydantic-ai, reusing job_matcher directly) and Node 20 (to actually run index.js). Wired into docker-compose.yaml as agent-service; chat-demo now points at it by compose network name instead of falling back to host.docker.internal.
Real bug found while picking port 6000 specifically: it's on the WHATWG Fetch spec's "bad ports" blocklist (the historical X11 port) — Node's native fetch() and every browser refuse to connect to it outright. wget from inside a container worked fine, masking the problem at first; the playground's own /api/health route (which uses fetch()) silently reported a healthy backend as offline. Root-caused via docker exec ... node -e "fetch(...)", which surfaced the real "bad port" TypeError. Fixed by shifting the whole scheme to 6010-6014 (also clear of 6006/6007, already in local use by an unrelated Arize Phoenix container, and of 6665-6669/6697, also on the blocklist).
Verified end-to-end with real containers: agent-service's spawned MCP subprocess discovers all 3 tools, a real /chat/stream call produces a genuine tool-called answer, and a real /analyze submission through the containerized playground scores a live job posting correctly — the full REST + MCP Bridge + playground + chat-demo stack, all containerized, all on the corrected port range.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Detailed Changes: v0.5.0...v0.6.0
v0.5.0
v0.5.0 (2026-07-12)
This release is published under the MIT License.
Documentation
- openspec: Demo-stack and playground-ui-parity change specs (
25064fb)
add-demo-stack-and-playground: proposal/design/tasks/specs for the containerized mcp-chat-client image, the playground and openapi-docs Next.js apps, and the docker-compose wiring — written and approved before implementation per standing AI-DLC process, all bolts now implemented and verified (see tasks.md evidence).
fix-playground-ui-parity: two rounds of owner UAT correction on the playground's look and feel (industry-standard match colors, sticky submit, missing cover letter/copy-to-clipboard, then a full privacyshield-template-parity pass — nav rail, fonts, contrast) — both logged and implemented same-day per owner direction.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
-
readme: Document the demo stack, REST endpoints, env vars, cover-letter templates (
70bac56) -
Related repos: trimmed to mcp-chat-client (runtime dependency) and
ai-dlc (the methodology deck this repo's whole openspec workflow
follows) — privacyshield/ai-agents/templrgo were design-lineage notes
that had served their purpose and just added noise -
New REST endpoints table (backend + agent service), Key environment
variables table, and Cover letter templates section (default path,
placeholders, and the TEMPLATES_DIR override — verified against
prompts.py's actual render() function, not assumed) -
Quick start gained the docker compose up -d demo-stack block
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Features
- compose: Wire playground, openapi-docs, and chat-demo into the demo stack (
dd7515e)
docker compose up -d now brings up the full self-contained demo: the backend API, the containerized mcp-chat-client demo page, the playground, and the branded OpenAPI docs — each on its own port, chosen to avoid collisions with this machine's other locally-running compose projects.
Also pins platform: linux/amd64 and pull_policy: missing on every image+build service (api, cli, chat-demo). These images are published amd64-only; without the pin, Compose can't resolve an arm64 manifest on Apple Silicon and silently falls back to a local build instead of saying why — confirmed via docker compose pull, which surfaces the real reason plainly (a genuine architecture mismatch for agent-job-matcher's own image; a still-private GHCR package, pending a one-time manual visibility change, for mcp-chat-client's).
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
- openapi-docs: Add branded Next.js OpenAPI reference app (
531497d)
A second, separate Next.js app (its own port, not a tab inside the playground) rendering the backend's live OpenAPI document through Scalar, themed with the same Slack-purple accent as the rest of the demo suite. app/route.ts fetches /openapi.json server-side on every request and inlines it via Scalar's content field rather than url, so the browser never needs direct network access to the backend — only this app's own origin, which matters inside the compose network where the backend's address isn't browser-reachable. Additive to FastAPI's built-in /docs, not a replacement.
Also force-adds playground/.env.example and openapi-docs/.env.example, both silently dropped from the prior playground commit by create-next-app's default .env* gitignore pattern — they're plain non-secret templates (just API_URL) matching the root repo's own tracked .env.example convention.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
- playground: Add Next.js playground for visual, evidence-grounded fit reports (
624dcba)
Resume upload + one-to-many job links on the left, a compact Grafana/ PowerBI-styled accordion of scored report cards on the right — a non-chat way to see the 40/20/20/20 breakdown and evidence quotes this project's whole scoring model produces. Server-side route handlers proxy to the backend's existing POST /analyze and GET /health, keeping API_URL out of client-side code entirely.
Layout, colors, and chrome are a deliberate port of privacyshield's Sidebar/App structure (colored collapsible nav rail, header with a live backend-status pill, Card/Label form composition) rather than a new design, per owner direction during UAT. Match-status colors follow the standard green-to-red grading scale, not the brand purple used elsewhere for chrome. Cover letters render in a collapsible section with copy-to-clipboard.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Detailed Changes: v0.4.2...v0.5.0
v0.4.2
v0.4.2 (2026-07-12)
This release is published under the MIT License.
Bug Fixes
- agent-service: Tool-call notices as a distinct field, not concatenated text (
373434c)
Owner-reported bug: the progress notice ("🔧 analyze_job_fit...") was literally prepended into the answer text, with no distinct visual treatment - the chat just showed one concatenated blob.
- app.py: tool notices now emit a distinct {"action": ""} SSE field (with a friendly per-tool label map) instead of folding "🔧 {name}...\n" into content. Additive to the ctms wire contract - a client ignoring the field behaves exactly as before. - test_app.py updated: asserts no "🔧" ever appears in content, and that an "action" event carries the tool notice instead.
README: added Tech Stack and Related Repos sections, including an
honest answer to "are we using Pydantic AI Gateway" - we're not: model ids resolve directly to each provider via pydantic-ai's own provider integrations, not through Pydantic's hosted gateway/routing product (a real, separate thing, now part of Pydantic Logfire). Noted as an additive future option, not a current gap.
A second, deeper issue was found while verifying this fix (the action label isn't yet genuinely live - it arrives alongside the first content chunk rather than while the tool call is in flight, confirmed via Playwright instrumentation on both fast and ~11s tool calls) and is logged as a defect spec in mcp-chat-client's openspec, not fixed here - it needs a real async-generator concurrency change.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.4.1...v0.4.2
v0.4.1
v0.4.1 (2026-07-12)
This release is published under the MIT License.
Bug Fixes
- observability: Configure() must load .env itself, not rely on call order (
6aa788c)
Owner configured ARIZE_SPACE_ID/ARIZE_API_KEY for real and got zero telemetry. Root cause: configure() read env vars directly, depending on resolve_model() having already called load_dotenv() earlier in the process - true in the CLI/pipeline path (resolve_model runs before the root span opens), false in the agent service's /chat/stream handler, where start_span() fires before any model-resolving code runs. configure()'s result is cached for the process's lifetime, so reading a pre-.env environment once meant Arize/OpenObserve were silently dead for that entire server process, however long it ran.
Reproduced directly (a clean subprocess calling configure() before anything else resolved False for sink activation despite correct .env values on disk) and fixed: configure() now calls the same ensure_env_loaded() config.py already exposes (renamed from _ensure_env_loaded, now a shared utility instead of module-private).
Verified via the real root_span/traced API in a clean process, not a hand-rolled OTel script: spans now reach otlp.arize.com with HTTP 200.
New regression test pins the exact failure mode. Fixing it surfaced a second issue in the existing telemetry test fixture: it deleted Arize env vars to simulate a clean environment, but configure()'s new .env load faithfully repopulated them from the real .env on disk (deleting a var just makes it "unset" from load_dotenv's perspective) - fixed by also freezing config's _loaded flag during those tests.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Testing
- agent-service: Cover conversation-history threading (
7aaf0cd)
Existing stub fixtures needed the new history parameter added to run_chat()'s signature (from the prior commit, bundled in with the CI race fix by an add -A - the code change was correct, this fills in its test coverage): history-threaded-through, missing-history-is-empty-list, and _to_model_history's dict-to-pydantic-ai-message conversion.
Live-verified end to end (not just unit tested): a follow-up question in the same session answered correctly from conversation memory alone (59/100 + the specific gap, no re-invoked tool call); the identical question with no history in a fresh session correctly said it hadn't given a score, ruling out a lucky guess.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.4.0...v0.4.1
v0.4.0
v0.4.0 (2026-07-11)
This release is published under the MIT License.
Bug Fixes
- ci: Re-sync to current main before semantic-release runs (
23f1bc7)
The shared concurrency group only serializes execution with graphify.yml
- it doesn't stop that job's push from landing on main first while this job's checkout stays pinned to the original trigger SHA. Add a git fetch + reset --hard origin/main immediately before invoking semantic-release so it always computes/commits/pushes against the actual current tip, not a stale snapshot.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
- ci: Serialize release and graphify pushes to stop a real race (
24de086)
release.yml's semantic-release push and graphify.yml's graph-commit push both fire on every push to main but lived in separate concurrency groups, so they could run in parallel and race for the same ref — observed twice today: the Bolt 10 feat commit's release run was rejected non-fast-forward because graphify's push landed on main first, mid-run, and a plain gh run rerun couldn't fix it (it replays the original stale checkout, not current main).
Both workflows now share one concurrency group (main-writers, cancel-in-progress: false) so they queue instead of racing.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Features
- backend: Cover letter rendering with deterministic candidate identity (
7163d29)
Bolt 10 of add-job-matcher-cli (revision 7) - offline suite now 109 passed (13 new tests), live smoke on openai:gpt-5.4-mini renders the full header with zero operator configuration:
- candidate.py: deterministic regex extraction of name/email/phone/ github/linkedin/website from the already-extracted resume text - no third LLM call, no grounding guard needed (a regex match is definitionally a literal substring of the source). Computed once per run, reused across every job in the fan-out. - job_matcher/templates/cover_letter.txt ships as package data; load_template() gains the same package-default fallback load_prompt() already has, so cover-letter rendering activates with no operator setup (correction to the earlier "no package default, missing template degrades to plain text" design) - pipeline.py: identity + a run-level date threaded through _job_task -> _cover_letter_text(), which builds the Re: <title> [at ] line and renders through the template - Real bug caught and fixed during implementation: the URL scanner's email-exclusion lookbehind blocked matching a domain AFTER an email's @, but not the email's own local-part before it (e.g. "sam.lee" in
"sam.lee@example.com" briefly misread as a website) - fixed by blanking email matches out of the text before URL scanning
Proposal status: all ten bolts now implemented; VERIFIED still awaits only the two owner manual evidence items (Claude Desktop mount, chatbot UI run).
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.3.2...v0.4.0
v0.3.2
v0.3.2 (2026-07-11)
This release is published under the MIT License.
Bug Fixes
- agent-service: Upload response includes content field for chat fold-in (
b2588d0)
Verifying the agent service's upload flow against the actual neutral chatbot (github.com/senthilsweb/mcp-chat-client) surfaced a real gap: its UploadResponse contract folds a content string into the next chat message and never reads path — our /upload only returned path/filename/message, so the model would never learn where an uploaded file landed even after the widget's own dead-code bug (fixed upstream in that repo) was corrected.
- /upload now also returns
content: "Resume uploaded — server path: ", the field the widget's contract actually relays into chat - Full round trip verified end-to-end at the wire level (widget's exact request/response handling replicated): upload -> fold content into message -> analyze_job_fit tool call -> grounded 78/100 good_match answer -> done. Existing test asserting body["path"] unaffected; agent-service suite still 6/6, backend 96/96. - openspec: ADR 0001 and design.md/spec.md carry dated corrections (not silent rewrites) documenting the gap and the fix; tasks.md distinguishes this wire-level proof from the still-open manual browser-UI evidence item
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Chores
- compose: Inline all env vars alongside env_file (
a6cb568)
Every variable listed in a shared x-jobmatcher-env anchor with ${VAR:-default} interpolation — .env stays the source of truth, the compose file documents every knob, and any value can be overridden per-run. Adds a named uploads volume pre-wired for the future containerized agent service (ADR 0001 upload flow).
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Detailed Changes: v0.3.1...v0.3.2