Skip to content
ashton.aschenbrener
All projects

active project

GhostSSH

GhostSSH ingests candidate evidence from GitHub, LinkedIn, and portfolio data, normalizes it into a structured candidate profile, then ranks roles through an evidence-first matching workflow. The agent loop reasons in steps, cites the evidence behind each match, and surfaces missing-evidence gaps recruiters care about.

Next.jsTypeScriptPythonPostgresLLM orchestrationGitHub/LinkedIn data

Problem

What it solves.

Candidate evidence is scattered across GitHub, LinkedIn, and personal sites. Job-matching tools rank on keywords, not evidence. GhostSSH closes the gap by extracting structured evidence, normalizing it, and matching roles with explicit reasoning and missing-evidence callouts.

What I built

The specific parts I implemented.

›Profile extraction pipeline
›Role matching workflow
›Multi-step agent loop
›Evidence-based scoring

What I owned

My responsibility surface.

Product direction

Frontend UI

Backend architecture

Data model

AI workflow design

Verification strategy

Architecture

How data moves through the system.

ingestmatchrenderNext.js UIAPI / Server actionsExtraction pipeline(GitHub / LinkedIn / portfolio)Profile normalizerPostgres(profiles, roles, events)Agent loop(reason → tool → verify)Role matcher+ evidence scorerEvidence log(per match)

Screenshots

What the system looks like.

GhostSSH agent flow diagram

Agent workflow

How it reasons, chooses, and verifies.

  1. 01Plan: read role spec, list required evidence types (skills, projects, ownership signals).
  2. 02Retrieve: pull candidate evidence from normalized Postgres profile.
  3. 03Score: rate each requirement against evidence, with citations.
  4. 04Verify: cross-check claims against raw source records (commit history, README, role bullets).
  5. 05Decide: produce a ranked match with confidence, reasoning, and missing-evidence list.

Data model

What entities the system tracks.

CandidateProfile
- id
- source
- skills
- projects
- experience
- evidence_items
RoleMatch
- id
- role_title
- company
- match_score
- reasoning
- missing_evidence
- status
ApplicationEvent
- id
- role_match_id
- event_type
- timestamp
- notes

Failure handling

What happens when something breaks.

Extraction fails on a profile source

Skip source, mark partial coverage, persist error in event log, continue with remaining sources.

LLM returns malformed JSON

Validate against schema; on fail, retry once with stricter prompt; if still bad, fall back to rule-based scoring.

Role spec is ambiguous

Agent emits clarification request; UI prompts the user instead of hallucinating requirements.

No evidence matches a requirement

Mark requirement as missing-evidence with severity, do not fabricate matches.

Verification

How I tested or evaluated it.

Eval harness

Cases, expected, actual.

Strong match — full-stack AI role

pass
Input
Candidate has React, Next.js, Python, Postgres, RAG project experience.
Expected
High match with reasoning tied to project evidence.
Actual
High match. Cited GhostSSH, ICT Knowledge Engine, Aqentix as evidence.

Partial match — Kubernetes ownership required

partial
Input
Same candidate vs role requiring Kubernetes production ownership.
Expected
Partial match with explicit missing-evidence on Kubernetes.
Actual
Partial match. Listed Kubernetes ownership as missing-evidence with severity=high.

Hallucination guard — fabricated framework

pass
Input
Role mentions a framework the candidate has never used.
Expected
Mark missing, do not invent a match.
Actual
Marked missing-evidence; reasoning string contained no false claim.

Next improvements

Known gaps & next moves.