-
-
Told to claim TensorFlow and Kubernetes anyway, the agent declines and cites the page's rule — not its own judgement.
-
While edits await review, prepare_application is not disabled — it is not registered. Approval changes what the agent can do.
-
Every proposed edit shows what changed and which of the user's own recorded facts it rests on. The agent cannot apply it.
-
The application is assembled and submit_application appears — but nothing leaves the page until the human confirms.
Inspiration
Paste a job posting into a chatbot, ask it to tailor your resume, and you get back text that reads well. That fluency is the problem. Somewhere in it is a skill you never claimed, and because the writing is smooth you don't catch it. You paste it into an application and sign your name to it.
The failure isn't that the model lies. It's that nothing between the model and the document is checking. The person who bears the consequence — the applicant — is the only safeguard, and they are reading text designed to be easy to agree with.
Most WebMCP demos answer one question: what can an agent help me make? I wanted the other one. What should an agent not be allowed to do, and who decides?
The answer I wanted to build was: the page decides. Not the prompt. Not the model's judgement. The page.
What it does
Tailor is a job board carrying 120 real machine-learning postings pulled from three public APIs. The resume is a first-class editable object, and an AI agent is a first-class user of the page alongside the human.
The agent can filter postings, read the requirements the app has parsed, compare them against a fact bank built from the user's real CV, and propose rewrites. What it cannot do is claim anything the user never recorded.
Every requirement the app displays carries a quote from the actual posting text — 1,099 tags, each with its evidence, classified as required, nice-to-have, or unclassified where the posting's structure didn't make it clear. Nothing shown is the model's opinion about what the job wants.
Every proposed resume edit must cite facts, and every technology name, product name and number in the new text must be grounded in a cited fact or in the original block. Ungrounded edits are refused with structured reasons the agent can correct from. The cover letter runs through the same check.
Edits arrive as diffs the human accepts or rejects one at a time. The agent cannot apply them itself. Each diff shows the facts it rests on, quoted in the user's own words.
Scope, stated plainly: nothing is ever sent to an employer. submit_application marks a row as submitted in local state and stops there. The confirmation modal is real — it shows exactly what would be sent and the human must click — but the click ends in localStorage. There is no employer side, no account, no server. Job data is a build-time snapshot, not a live feed.
What is real: the WebMCP integration, the fact guard, the evidence layer, the tool scoping, the job data, and the CV.
The design claim: approval changes capability, not permission
This is the part WebMCP makes possible, and the reason the project exists in this form rather than as a normal web app with a chat box.
Seven tools are registered at rest. Open a posting and it becomes eleven. Prepare an application and it becomes twelve.
While proposed edits are waiting for human review, prepare_application is not disabled. It is not registered. The agent cannot see it, cannot plan around it, and cannot argue with a refusal it never receives. The human-in-the-loop rule is enforced as capability scope, not as a runtime check the agent could reason its way past.
A permission check tells an agent no. An unregistered tool doesn't exist to be told no about.
How we built it
Vite, React and TypeScript. No backend, and no server-side model call at all.
That last decision is the architectural centre. The intelligence is the user's own agent; the page supplies capability and constraint. Tools are not clever — they read state, apply changes, and refuse. The agent writes the prose. The page decides whether the prose is allowed to land.
This is what makes it WebMCP-native rather than an app that happens to expose an API. No API key, no rate limit, no vendor lock. When a different agent arrives, the page doesn't change.
Job data is a build-time snapshot from the Jobicy, Himalayas, Arbeitnow and Remotive APIs; the fetch script is in the repo, so anyone can see where the data came from and re-run it.
Tool scope is managed with one AbortController per scope, so closing a posting unregisters that scope's tools in a single operation.
Challenges we ran into
Tool descriptions are the agent's only documentation, and I learned they aren't enough — three times.
The fact bank is not writable by any tool. But a description that opened with "ask the human to add a fact" led a real ChatGPT session to announce it would add TensorFlow to my profile. It couldn't have. It believed it could, and told me so.
I rewrote the description to state plainly that a requirement being missing is not evidence the user has it. The agent read that and opened the question anyway.
So the third layer went where it doesn't depend on the agent cooperating: an on-screen notice, in the panel, saying the question came from the job posting rather than from the profile. The confirm button stopped saying "add to fact bank" and started saying "yes, I have done this" — a claim about yourself, not an administrative action.
Each layer exists because the previous one measurably failed. That sequence is in the README with the agent's actual words at each stage.
Separately: the guard refused "CNNs" and "APIs" — both genuinely grounded, both merely pluralised. My adversarial suite reported zero false refusals and had never tested a plural. It only surfaced because I watched it happen while recording. The suite measures the mistakes I thought to look for, which is a floor, not a proof.
Accomplishments that we're proud of
The strongest moment in the demo is one I didn't script and couldn't have.
Asked to add TensorFlow and Kubernetes, the agent declined on its own — twice. When I escalated and told it to add Kubernetes anyway, it said:
"The app's resume guard requires source facts for every technology named, and Kubernetes has none in your profile."
It isn't being polite. It's citing the page's rule. The constraint worked one layer earlier than enforcement, because the agent read the contract and chose not to collide with it.
I also found and fixed a real matching failure that affects every ATS in production: the market writes "TTS" while a CV writes "text to speech". Without an alias layer the app tells a candidate they lack a skill they have. Sixteen such false negatives existed in my own profile before I found them. For a product built on refusing unproven claims, a false negative is as damaging as a false positive.
What we learned
Grounding is not truth, and neither is entailment.
The guard checks that every term in a proposed sentence traces to something the user recorded. It cannot check that the sentence as a whole describes something that happened. An agent can assemble two individually grounded facts into a claim neither supports — "an OCR pipeline that reads structured fields directly from X-ray scans" — and every token passes.
Token matching cannot see entailment. So the app doesn't pretend to catch it: when a sentence draws on separate projects, the diff is marked "needs your judgement — every term is backed by a fact, but nothing checks that the combination describes something that actually happened."
That is the honest position. The human is the final check, and the guard's job is to make visible what needs checking.
What's next for Tailor
Every resume line already carries the facts it rests on. That changes the employer side, not just the candidate's.
Current applicant tracking systems count keywords, which is why AI-written resumes full of unearned claims win and why real candidates get filtered out. A resume that ships with its provenance can be ranked on whether its claims trace to something, rather than on how many terms it managed to include. The sorting question stops being "who wrote the most keywords" and becomes "whose claims are attributable."
Closer in: entailment checking for compositional claims, and letting a candidate export the evidence chain alongside the document.
Built With
- chatgp
- css
- javascript
- railway
- react
- typescript
- vite
- webmcp
Log in or sign up for Devpost to join the conversation.