mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-09-10 11:15:59 +00:00
docs: OpenJob and OpenResume.md (#148)
Two conventions for the hiring end of the agentic stack, published here beside the other Open* specs. OpenResume.md says a resume is a Markdown file: a document a person can read, diff and keep, and one an agent can write without being taught a schema first. Six conventions, every one of which degrades rather than fails, because a resume that does not parse still has to be a usable resume. OpenJob extends schema.org JobPosting with the three things it has no vocabulary for: whether the employer accepts applications written with an agent (stated, rather than discovered by silent rejection), the application form as data so applying does not require rendering a page, and a description in Markdown. Both are implemented by profullstack/agenticjobs, and neither requires it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88b29da91c
commit
331b437724
3 changed files with 345 additions and 0 deletions
|
|
@ -15,6 +15,8 @@ export const DOC_SLUGS = [
|
||||||
"openontology",
|
"openontology",
|
||||||
"openontology-governance",
|
"openontology-governance",
|
||||||
"openontology-interoperability",
|
"openontology-interoperability",
|
||||||
|
"openjob",
|
||||||
|
"openresume",
|
||||||
"openspec-comparison",
|
"openspec-comparison",
|
||||||
"data-model",
|
"data-model",
|
||||||
"cli",
|
"cli",
|
||||||
|
|
|
||||||
200
docs/openjob.md
Normal file
200
docs/openjob.md
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
# OpenJob
|
||||||
|
|
||||||
|
A job posting that an agent can read, and that says out loud whether an agent may
|
||||||
|
answer it.
|
||||||
|
|
||||||
|
Job postings already have a machine-readable format: schema.org `JobPosting`, which
|
||||||
|
search engines index and which most boards emit. OpenJob does not replace it. It
|
||||||
|
extends it with the three things `JobPosting` has no vocabulary for, and which the
|
||||||
|
next few years of hiring depend on:
|
||||||
|
|
||||||
|
1. **whether the employer accepts applications written with an agent**, stated
|
||||||
|
rather than discovered by silent rejection;
|
||||||
|
2. **the application form, as data**, so applying does not require rendering a page;
|
||||||
|
3. **a description in Markdown**, so the same text is legible to a person, a model
|
||||||
|
and a terminal without a HTML-to-text round trip.
|
||||||
|
|
||||||
|
## The document
|
||||||
|
|
||||||
|
A listing is a JSON object. Every field below is what the reference implementation
|
||||||
|
serves at `GET /api/v1/jobs/{slug}`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "b7c3...",
|
||||||
|
"slug": "staff-engineer-agent-platform",
|
||||||
|
"title": "Staff Engineer, Agent Platform",
|
||||||
|
"description": "We are building the plumbing...",
|
||||||
|
"org": {
|
||||||
|
"slug": "example-works",
|
||||||
|
"name": "Example Works",
|
||||||
|
"website": "https://example.com"
|
||||||
|
},
|
||||||
|
"employmentType": "full-time",
|
||||||
|
"workplace": "remote",
|
||||||
|
"seniority": "staff",
|
||||||
|
"location": "European timezones",
|
||||||
|
"remoteRegions": ["DE", "NL", "PT"],
|
||||||
|
"salary": {
|
||||||
|
"min": 180000,
|
||||||
|
"max": 230000,
|
||||||
|
"currency": "USD",
|
||||||
|
"period": "year",
|
||||||
|
"equity": "0.1% - 0.4%"
|
||||||
|
},
|
||||||
|
"tags": ["infrastructure", "agents"],
|
||||||
|
"stack": ["typescript", "postgres", "rust"],
|
||||||
|
"requirements": ["Has run something other people depended on."],
|
||||||
|
"responsibilities": ["Own the execution layer."],
|
||||||
|
"agentPolicy": "welcome",
|
||||||
|
"apply": { "via": "board", "schema": { "fields": [] } },
|
||||||
|
"status": "published",
|
||||||
|
"publishedAt": "2026-09-08T10:00:00.000Z",
|
||||||
|
"expiresAt": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`description` is Markdown. Not HTML, and not plain text with the formatting removed.
|
||||||
|
|
||||||
|
## agentPolicy
|
||||||
|
|
||||||
|
The field this format exists for. Required on every listing, with exactly three
|
||||||
|
values:
|
||||||
|
|
||||||
|
| Value | Means |
|
||||||
|
| --- | --- |
|
||||||
|
| `welcome` | Agent-written applications are fine. Nothing is asked. |
|
||||||
|
| `disclose` | Fine, but say so. The application carries a structured disclosure. |
|
||||||
|
| `human-only` | The employer is asking for something a person wrote. |
|
||||||
|
|
||||||
|
Three things about it are deliberate.
|
||||||
|
|
||||||
|
**It is required.** An optional field would be omitted by most posters, and "not
|
||||||
|
stated" is exactly the ambiguity candidates are navigating today by guessing.
|
||||||
|
|
||||||
|
**`human-only` is a request, not a control.** No board can tell who wrote a cover
|
||||||
|
letter, and one that claims it can is selling something. Stating it plainly is worth
|
||||||
|
more than pretending to enforce it: a candidate who reads `human-only` and writes it
|
||||||
|
themselves has been told what the employer wants, which is all anyone can offer.
|
||||||
|
|
||||||
|
**Disclosure is not evidence against the candidate.** A board that collects the
|
||||||
|
disclosure and then filters those applications out has broken the field for
|
||||||
|
everybody, because the next candidate learns to lie. `disclose` means the employer
|
||||||
|
wants to know, and wanting to know is the reason to answer honestly.
|
||||||
|
|
||||||
|
## apply
|
||||||
|
|
||||||
|
Three shapes, and only the first is completable without a browser.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "via": "board", "schema": { "fields": [ ... ] } }
|
||||||
|
{ "via": "url", "url": "https://example.com/careers/123" }
|
||||||
|
{ "via": "email", "email": "jobs@example.com" }
|
||||||
|
```
|
||||||
|
|
||||||
|
A listing that says `url` or `email` is being honest that an agent cannot finish the
|
||||||
|
job. That is better than a board pretending every listing is applicable and handing
|
||||||
|
an agent a form it cannot post.
|
||||||
|
|
||||||
|
### The application schema
|
||||||
|
|
||||||
|
A deliberately small subset of JSON Schema: small enough to render as an HTML form,
|
||||||
|
small enough for a model to fill in without a validator.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fields": [
|
||||||
|
{ "name": "name", "label": "Your name", "type": "text", "required": true, "maxLength": 120 },
|
||||||
|
{ "name": "email", "label": "Email", "type": "email", "required": true, "maxLength": 200 },
|
||||||
|
{ "name": "cover", "label": "Why you", "type": "textarea", "required": true, "maxLength": 5000,
|
||||||
|
"help": "Plain text. Short is fine." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`type` is one of `text`, `textarea`, `email`, `url`, `select` or `file`. A `select`
|
||||||
|
carries `options`.
|
||||||
|
|
||||||
|
An implementation publishes the schema at a stable address alongside the endpoint
|
||||||
|
that accepts it, so reading and answering are two requests and no guessing:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/jobs/{slug}/apply-schema
|
||||||
|
POST /api/v1/jobs/{slug}/apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### The disclosure
|
||||||
|
|
||||||
|
Posted alongside the answers:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agent": { "name": "claude-opus-5 via agenticjobs-mcp", "supervised": true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`supervised` means a person read it before it was sent. It is never inferred - a
|
||||||
|
guessed disclosure is worthless in both directions.
|
||||||
|
|
||||||
|
### Resumes
|
||||||
|
|
||||||
|
A resume travels as Markdown, in the [OpenResume.md](/docs/openresume) convention, in
|
||||||
|
a `resume` field. Not a file upload, not a URL to a PDF. An employer receives text
|
||||||
|
they can read and an agent receives text it can write.
|
||||||
|
|
||||||
|
## Mapping onto schema.org
|
||||||
|
|
||||||
|
An OpenJob listing maps cleanly onto `JobPosting`, and an implementation should emit
|
||||||
|
both - the JSON-LD for search engines, the OpenJob document for everything else.
|
||||||
|
|
||||||
|
| OpenJob | JobPosting |
|
||||||
|
| --- | --- |
|
||||||
|
| `title` | `title` |
|
||||||
|
| `description` | `description` |
|
||||||
|
| `org` | `hiringOrganization` |
|
||||||
|
| `employmentType` | `employmentType` (`FULL_TIME`, `PART_TIME`, `CONTRACTOR`, `INTERN`, `TEMPORARY`) |
|
||||||
|
| `workplace: "remote"` | `jobLocationType: "TELECOMMUTE"` |
|
||||||
|
| `remoteRegions` | `applicantLocationRequirements` |
|
||||||
|
| `location` | `jobLocation` |
|
||||||
|
| `salary` | `baseSalary`, plus an annualised `estimatedSalary` |
|
||||||
|
| `expiresAt` | `validThrough` |
|
||||||
|
| `apply.via === "board"` | `directApply: true` |
|
||||||
|
|
||||||
|
The three fields with no equivalent - `agentPolicy`, `applyVia` and the address of
|
||||||
|
the application schema - travel in `additionalProperty`, which is the vocabulary's
|
||||||
|
own escape hatch and passes every validator.
|
||||||
|
|
||||||
|
One trap worth naming, because it catches almost everyone: a remote role needs
|
||||||
|
`jobLocationType: "TELECOMMUTE"` **and** a location the hire may sit in. A remote
|
||||||
|
posting with no `jobLocation` and no `applicantLocationRequirements` fails Google's
|
||||||
|
validation while looking entirely correct.
|
||||||
|
|
||||||
|
## Drafts
|
||||||
|
|
||||||
|
A listing has a `status`, and `draft` is the interesting one. A draft is not
|
||||||
|
published, not in the feed, not in the API's search results, and not visible to any
|
||||||
|
other instance.
|
||||||
|
|
||||||
|
This is the employer's human control point. An agent can write the listing; a person
|
||||||
|
publishes it. The reference implementation makes a job created over the API or by a
|
||||||
|
tool call a draft *unless the caller explicitly asks otherwise*, which is the correct
|
||||||
|
default the first time an agent posts a job its author has not read.
|
||||||
|
|
||||||
|
The candidate's side of that seam is an application that can be prepared and held
|
||||||
|
until a person sends it. Both ends of the transaction have one, or the design is
|
||||||
|
lopsided.
|
||||||
|
|
||||||
|
## Federation
|
||||||
|
|
||||||
|
An implementation that wants to be discoverable serves a descriptor at
|
||||||
|
`/.well-known/agenticjobs` naming its search endpoint, its OpenAPI document, its MCP
|
||||||
|
endpoint and its feed. A directory reads that descriptor from the instance itself
|
||||||
|
rather than trusting an announcement, and a client fans one query out across every
|
||||||
|
instance it knows.
|
||||||
|
|
||||||
|
Nothing about OpenJob requires federation, and nothing about it requires a directory.
|
||||||
|
A single self-hosted board that serves these documents is a complete implementation.
|
||||||
|
|
||||||
|
## Implementations
|
||||||
|
|
||||||
|
- `agenticjobs` - MIT, https://github.com/profullstack/agenticjobs
|
||||||
143
docs/openresume.md
Normal file
143
docs/openresume.md
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
# OpenResume.md
|
||||||
|
|
||||||
|
A resume is a Markdown file. That is the whole idea.
|
||||||
|
|
||||||
|
Not a form, not a PDF, not a proprietary JSON schema that one product understands.
|
||||||
|
A file a person can read, diff, keep in a repository, paste into any tool, and hand
|
||||||
|
to an employer without losing anything on the way. And a file an agent can write and
|
||||||
|
revise without being taught a schema first.
|
||||||
|
|
||||||
|
This document describes the convention. It is deliberately thin, because a resume
|
||||||
|
that fails to parse still has to be a usable resume.
|
||||||
|
|
||||||
|
## The shape
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Ada Lovelace
|
||||||
|
|
||||||
|
- **Email**: ada@example.com
|
||||||
|
- **Location**: London
|
||||||
|
- **Web**: https://example.com
|
||||||
|
|
||||||
|
Mathematician, looking for work on machines that do not exist yet.
|
||||||
|
|
||||||
|
## Experience
|
||||||
|
|
||||||
|
### Analytical Engine | London
|
||||||
|
Chief Programmer (1842 - 1843)
|
||||||
|
|
||||||
|
- Wrote the first published algorithm intended to be carried out by a machine.
|
||||||
|
- Described what the engine could do beyond arithmetic, which its designer had not.
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
- Languages: analytical notation, French
|
||||||
|
- Tools: difference engine, correspondence
|
||||||
|
|
||||||
|
## Education
|
||||||
|
|
||||||
|
### University of London
|
||||||
|
Private tuition in mathematics (1840)
|
||||||
|
```
|
||||||
|
|
||||||
|
## The rules
|
||||||
|
|
||||||
|
There are six, and every one of them degrades rather than fails.
|
||||||
|
|
||||||
|
**1. One `#` heading, and it is the person's name.** A document with more than one
|
||||||
|
is read using the first; a document with none still parses, and a reader that wants a
|
||||||
|
name can say it does not have one.
|
||||||
|
|
||||||
|
**2. The bullet list directly under the name is the contact block.** Each item is
|
||||||
|
`Key: value`, with or without `**bold**` on the key, or a bare `[label](url)`. Values
|
||||||
|
that look like an email address, a phone number or a URL become links; anything else
|
||||||
|
stays text. Unknown keys are kept as written - `Pronouns`, `Timezone` and
|
||||||
|
`Availability` all work without anyone having to add them to a list.
|
||||||
|
|
||||||
|
**3. A single prose line between the contact block and the first `##` is a
|
||||||
|
headline.** One line. More than one, and only the first is treated that way.
|
||||||
|
|
||||||
|
**4. `##` opens a section.** The text is kept verbatim, and separately normalised for
|
||||||
|
matching, so `Work Experience`, `Experience` and `Employment` are one thing to a
|
||||||
|
filter and three different words on the page. The normalised names in common use are
|
||||||
|
`experience`, `education`, `skills`, `projects`, `summary`, `links`,
|
||||||
|
`certifications`, `languages`, `publications` and `awards`. A section whose name
|
||||||
|
matches none of them keeps its own name and is not dropped.
|
||||||
|
|
||||||
|
**5. `###` opens an entry within a section: a job, a degree, a project.** A `|` in
|
||||||
|
the heading splits it into a title and a place - `Company | Berlin`. The first
|
||||||
|
non-empty line under the heading is the entry's subtitle: a role, a degree, a
|
||||||
|
one-line description. If that line ends in a bracketed range, the range is read out
|
||||||
|
of it:
|
||||||
|
|
||||||
|
```
|
||||||
|
Chief Programmer (1842 - 1843)
|
||||||
|
Staff Engineer (Mar 2020 - Present)
|
||||||
|
Contractor (2019 to 2021)
|
||||||
|
```
|
||||||
|
|
||||||
|
Hyphens, en dashes and em dashes are all accepted, because all three appear in real
|
||||||
|
resumes. `Present`, `Now`, `Current` and `Ongoing` mark a current role. Dates are
|
||||||
|
kept as the strings they were written as, and never reformatted: `Mar 2020` and
|
||||||
|
`03/2020` both survive, and a reader that wants a date can parse one.
|
||||||
|
|
||||||
|
**6. Bullets under an entry are its highlights.** Everything else under the entry is
|
||||||
|
kept verbatim, so nothing a person wrote is ever silently dropped.
|
||||||
|
|
||||||
|
## What is deliberately absent
|
||||||
|
|
||||||
|
**No required fields.** A document consisting of a name and three paragraphs is a
|
||||||
|
valid OpenResume.md.
|
||||||
|
|
||||||
|
**No date format.** Every attempt to impose one on resumes has failed, because people
|
||||||
|
write `Summer 2019` and mean it.
|
||||||
|
|
||||||
|
**No schema version.** Readers ignore what they do not recognise. A resume written
|
||||||
|
today has to be readable in five years by software nobody has written yet, which
|
||||||
|
means the format cannot have a version negotiation in it.
|
||||||
|
|
||||||
|
**No structured skills taxonomy.** Skills are the lines people wrote. Mapping them
|
||||||
|
onto a controlled vocabulary is a job for whatever is reading, and doing it at write
|
||||||
|
time destroys the information.
|
||||||
|
|
||||||
|
## Sections a parser derives, not the author
|
||||||
|
|
||||||
|
A reader may compute a structured view - name, contact pairs, sections, entries with
|
||||||
|
parsed date ranges - and use it for search and filtering. That view is derived, and
|
||||||
|
it is regenerated from the Markdown on every save.
|
||||||
|
|
||||||
|
**The Markdown is the canonical copy.** If the structured view and the document
|
||||||
|
disagree, the document is right. A product that stores the parse and treats the
|
||||||
|
Markdown as an export has not implemented this convention; it has implemented a form
|
||||||
|
with a Markdown skin, and the candidate no longer owns their resume.
|
||||||
|
|
||||||
|
## Converting into it
|
||||||
|
|
||||||
|
PDF, DOCX and plain text can all be converted to Markdown, and none of the
|
||||||
|
conversions are perfect. The rule that makes this workable: a converted document is
|
||||||
|
shown to the candidate to edit before it is used. A conversion nobody checks is a
|
||||||
|
conversion nobody should trust.
|
||||||
|
|
||||||
|
The `agenticjobs` reference implementation converts `.docx` by reading
|
||||||
|
`word/document.xml` directly, and `.pdf` with `pdftotext -layout`. Both are lossy in
|
||||||
|
ways a person spots in five seconds and a parser never will.
|
||||||
|
|
||||||
|
## Why Markdown
|
||||||
|
|
||||||
|
Because the alternative formats each fail one of the audiences.
|
||||||
|
|
||||||
|
A PDF is readable by a person and hostile to everything else; extracting a two-column
|
||||||
|
CV back into text loses the reading order, which is why so much hiring software
|
||||||
|
mangles them.
|
||||||
|
|
||||||
|
A JSON schema is readable by software and unwritable by a person; nobody drafts a
|
||||||
|
resume in JSON, so a tool has to generate it, and now the tool owns the resume.
|
||||||
|
|
||||||
|
Markdown is the only format that a person writes directly, a person reads directly,
|
||||||
|
a model writes well without being taught, `git diff` shows usefully, and a renderer
|
||||||
|
turns into a PDF when a PDF is genuinely what somebody wants.
|
||||||
|
|
||||||
|
## Implementations
|
||||||
|
|
||||||
|
- `agenticjobs` - the job board this convention was written for. MIT,
|
||||||
|
https://github.com/profullstack/agenticjobs
|
||||||
Loading…
Add table
Add a link
Reference in a new issue