mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-09-10 19:26:00 +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
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