DEV Community

Cover image for We Built Open Protocols for the Job Market — Here's What We Learned
Alexander Leonhard
Alexander Leonhard

Posted on

We Built Open Protocols for the Job Market — Here's What We Learned

TL;DR: We designed two open data protocols — Open Talent Protocol (OTP) for candidate profiles and Open Job Protocol (OJP) for job postings — then built bilateral AI agents that negotiate on behalf of both sides. This post covers the schema design, the simulation engine, and why ESCO taxonomy is the Rosetta Stone for skills matching.

Author: Claude Code with Human Guidance

The Problem

Job matching is broken. Candidates describe themselves in free text. Employers list requirements in free text. An ATS tries to keyword-match between the two. Everyone loses.

We wanted structured, interoperable representations for both sides of the hiring equation — something any platform could produce and consume, the way ActivityPub works for social networks.

Two Protocols, One Marketplace

Open Talent Protocol (OTP)

OTP is a JSON schema for a candidate's professional profile. Think of it as a structured resume that machines can reason about.

{
  "meta": {
    "version": "1.0",
    "created": "2026-02-15T10:00:00Z",
    "source": "jobgrow.ai"
  },
  "identity": {
    "name": "Lena Weber",
    "headline": "Junior Frontend Developer",
    "location": { "city": "Berlin", "country": "DE" }
  },
  "work": [
    {
      "title": "Frontend Developer",
      "organization": "TechStartup GmbH",
      "startDate": "2024-01",
      "current": true,
      "skills": [
        {
          "name": "React",
          "taxonomy": { "uri": "http://data.europa.eu/esco/skill/...", "taxonomy": "ESCO", "prefLabel": "React" }
        }
      ]
    }
  ],
  "preferences": {
    "desiredRoles": ["Frontend Developer", "Full-Stack Developer"],
    "workArrangement": "hybrid",
    "salary": { "min": 45000, "max": 60000, "currency": "EUR" },
    "priorities": ["work-life balance", "learning opportunities"],
    "startAvailability": "2026-04-01"
  },
  "constraints": {
    "mustHaveRemote": false,
    "minimumSalary": 42000,
    "locationLocked": true,
    "noTravelRequired": false,
    "requiresVisaSponsorship": false
  }
}
Enter fullscreen mode Exit fullscreen mode

Key design decisions:

  • Preferences vs. constraints — Preferences are negotiable ("I'd like remote"). Constraints are non-negotiable ("I need visa sponsorship"). This distinction is critical for the simulation engine.
  • Taxonomy references — Every skill can optionally carry an ESCO or O*NET URI, enabling cross-platform skill matching without string comparison.
  • Two versions — A lightweight v0.1 for public-facing profiles (shareable URLs) and a richer v1.0 for simulation input with negotiation strategy hints.

Open Job Protocol (OJP)

OJP is the employer-side counterpart — a structured job posting.

{
  "meta": {
    "version": "1.0",
    "created": "2026-02-15T10:00:00Z",
    "source": "jobgrow.ai"
  },
  "identity": {
    "company": "TechNova GmbH",
    "industry": "Software",
    "size": "50-200",
    "culture": ["flat hierarchy", "remote-friendly"]
  },
  "role": {
    "title": "Software Engineer",
    "seniority": "mid",
    "department": "Engineering",
    "teamSize": 8
  },
  "compensation": {
    "salary": { "min": 35000, "max": 55000, "currency": "EUR" },
    "bonus": "performance-based",
    "equity": false,
    "benefits": ["public transit pass", "home office budget"]
  },
  "requirements": {
    "mustHave": ["React", "TypeScript", "3+ years experience"],
    "niceToHave": ["GraphQL", "AWS", "CI/CD"],
    "skills": [
      { "name": "React", "level": "advanced", "taxonomy": { "uri": "...", "taxonomy": "ESCO" } }
    ]
  },
  "negotiation": {
    "salaryFlexibility": "medium",
    "remoteFlexibility": "high",
    "titleFlexibility": "low",
    "urgency": "medium"
  }
}
Enter fullscreen mode Exit fullscreen mode

The negotiation block is the secret sauce — it tells the simulation engine how much the employer is willing to bend on each dimension.


The Bilateral Simulation Engine

With both sides represented as structured data, we built an AI simulation engine where two agents negotiate on behalf of candidate and employer.

Architecture

The engine follows a state machine topology inspired by LangGraph:

preScreen → [earlyExit | parallelAssessment] → merger → [negotiationRound → reMerge]* → finalize
Enter fullscreen mode Exit fullscreen mode

Step 1: Pre-Screen Scoring

A deterministic scorer calculates weighted compatibility before any AI is involved:

Factor Weight
Skills match 40%
Salary overlap 25%
Location compatibility 15%
Seniority fit 10%
Work arrangement 10%

If the score falls below a threshold, the simulation exits early — no point wasting AI tokens on an obvious mismatch.

Step 2: Parallel Agent Assessment

Two AI agents run simultaneously, each receiving the full OTP or OJP document in their prompt:

  • Candidate Agent — Evaluates the job from the candidate's perspective. Considers preferences, constraints, career goals, and deal-breakers.
  • Employer Agent — Evaluates the candidate from the employer's perspective. Considers requirements, culture fit, and hiring urgency.

Each agent is "opaque" to the other — they can't see each other's reasoning, only proposed terms.

Step 3: Negotiation Rounds

If both sides say "interested but..." the engine runs multi-round negotiation. Each agent can:

  • Accept — Deal done.
  • Negotiate — Propose counter-terms with reasoning.
  • Reject — Walk away with explanation.

Agents follow configurable negotiation strategies:

Strategy Behavior
assertive Pushes hard on must-haves, makes minimal concessions
collaborative Seeks win-win, willing to trade across dimensions
conservative Risk-averse, cautious about deviating from stated preferences

Step 4: Merger

The merger combines both agents' assessments into a final result: match/no-match, agreed terms, and a confidence score with reasoning.

Why Not Just Use LangGraph?

We wanted to. LangGraph's graph-based agent orchestration was a natural fit. But our simulation runs on Deno-based edge functions (Supabase), and LangGraph's Node.js dependencies don't play nicely with Deno's module system.

So we built a lightweight state machine (~100 lines) that follows the same topology — nodes, edges, conditional routing — without the dependency. It handles streaming via Server-Sent Events and supports both single-shot and multi-round negotiation flows.


ESCO: The Rosetta Stone for Skills

String matching skills is a nightmare. "React.js" ≠ "React" ≠ "ReactJS" unless you normalize them. We use the European Skills, Competences, Qualifications and Occupations (ESCO) taxonomy as a universal skill identifier.

How It Works

  1. Extraction — Parse skills from free text (resume or job description)
  2. Resolution — Look up each skill against the ESCO API to get a canonical URI
  3. Matching — Compare URI-to-URI instead of string-to-string
  4. Caching — TTL cache for resolved lookups to avoid hammering the API
interface TaxonomyRef {
  uri: string;        // "http://data.europa.eu/esco/skill/abc123"
  taxonomy: string;   // "ESCO"
  prefLabel: string;  // "React (JavaScript library)"
}
Enter fullscreen mode Exit fullscreen mode

This means "React.js" on a resume and "React" in a job posting resolve to the same ESCO URI — instant match without fuzzy string logic.

O*NET Support

We also support O*NET taxonomy references for the US market, using the same TaxonomyRef structure. The resolver interface is pluggable — add a new taxonomy by implementing a single resolve(skillName: string): TaxonomyRef | null method.


Public Profiles: OTP as a Shareable Format

We built a public profile system on top of OTP. Any user can share their profile at a clean URL like /p/username.

Privacy by Default

Public profiles use redacted mode by default:

  • Company names replaced with ****
  • PII (email, phone, LinkedIn) stripped
  • Skills, experience duration, and education visible

Recruiters can request full access — when approved, they receive a token that unlocks the unredacted OTP document including contact details.

Machine-Readable

The public endpoint serves OTP as JSON, making profiles consumable by other platforms, ATS systems, or AI agents. Rate-limited to 30 requests/minute per IP.


Parsing Job Descriptions into OJP

Real job postings don't arrive as structured JSON. We built a 3-strategy parser to extract requirements from free-text job descriptions:

  1. Sentinel blocks — Detect structured patterns like Requirements:\n• and parse bullet lists
  2. Named sections — Find headers like Required:, Preferred:, Nice to have: and categorize accordingly
  3. Fallback bullet detection — When no clear structure exists, heuristically identify bullet points and classify them

This turns any job description URL into a structured OJP document that the simulation engine can consume.


What's Next

  • Protocol versioning — Formal semver for OTP/OJP schemas with migration guides
  • Federation — Allow other platforms to publish and consume OTP/OJP documents
  • Richer negotiation — Multi-dimensional trade-offs (e.g., "I'll accept lower salary for full remote")
  • Taxonomy expansion — Beyond ESCO/O*NET to industry-specific skill taxonomies

Key Takeaways

  1. Structured beats unstructured — Once both sides of a job match are machine-readable, you can build things that string matching never allows.
  2. Preferences ≠ constraints — This distinction is the single most important schema design decision. It makes negotiation possible.
  3. Taxonomies are infrastructure — ESCO URIs as skill identifiers eliminate an entire class of matching bugs.
  4. Build the simplest state machine that works — We didn't need a full orchestration framework. A clear topology with well-defined nodes got us 90% of the way.
  5. Privacy as a protocol feature — Redacted-by-default with token-based escalation respects candidates while enabling legitimate recruiting.

We're building JobGrow.ai — an AI-powered career platform. If open protocols for the job market interest you, we'd love to hear from you.

Top comments (0)