Personal project · System & data model
An agent that reads job ads, decides which ones are worth applying for, picks the right résumé and fills in the employer's questions — under one rule: it is allowed to cost you the job, but it is never allowed to lie to get it.
No jargon. Here is what happens from the moment you ask it to look for work to the moment an application is submitted — and how it gets cheaper every time it runs.
You give it a kind of role — "AI operations", "IT support" — and it searches the job site the way you would.
It reads each job title and sorts it into one of five buckets. Most jobs are obviously wrong and are dropped on the title alone, without opening anything. It never drops one silently — it writes down which word made the decision.
Some titles are genuinely ambiguous — "Solution Designer" could be anything. Only those get read properly, because reading costs time and the other 90% don't need it.
There are three: one for AI and automation roles, one for business and systems analysis, one for general tech. It scores all three against the ad and sends the best fit — and records which one nearly won, so a bad choice is easy to spot later.
This is the expensive part. It tries the free options first — what the site already remembers, its own correction list, and answers it has learned before. Only a genuinely new question is sent to the AI.
Every answer — including the ones it wrote itself — is checked against what it's actually allowed to claim. If an answer would claim a certificate, a clearance or a language that isn't yours, it's blocked. It would rather lose the application.
A submitted application is logged with the CV it used and every answer it gave. A skipped job is logged too, with the reason. You can always ask it why.
When it has answered the same question the same way three times, that
answer becomes permanent. The next run never has to ask the AI again — so the agent gets
cheaper and more consistent the more you use it.
↺ loops back to step 5
An agent applying for jobs on your behalf has an obvious temptation: round the answers up and win more interviews. These are the guardrails against that, each in one sentence. Every one is a rule in code with a test behind it — not a polite request in a prompt.
Security clearances, certifications and working-with-children checks are hard-blocked unless confirmed in the profile — even if the form has pre-filled a "yes".
On any years-of-experience question the agent takes the lower bracket. Two years of IT support is answered as two, not "2–5".
SEEK pre-fills answers from past applications. Those are re-checked against 208 overrides and corrected before submission — the most common source of accidental overclaiming.
A Bookings Officer who used IT systems daily is not an IT Support Officer. The agent describes the role that existed, not the role that would score better.
Any language not listed in the profile defaults to the lowest proficiency option — closing a real bug where forms pre-filled "Native or Bilingual" for languages never spoken.
Each application is labelled — target fit, strategic stretch, worth a shot, or low-confidence default — so a weak application is visible as weak rather than hidden among the strong ones.
A skipped job records its classification, the signals that matched, the description score and the threshold it missed. Skips are auditable, so bad filtering is findable.
202 regression tests pin the behaviour, and --recheck-skips replays every past rejection through improved logic — so a fix reaches the jobs it already got wrong.
Every job runs through a fixed funnel. Most listings are eliminated on the title alone, for free; only genuinely ambiguous ones are worth reading the description for. Every decision — including every rejection — is written to the log with its reason.
Employer questions are where cost and honesty collide. Four sources are tried in order and the model is the last resort, not the first. The honesty guard sits across all four — a cached answer is not trusted merely because the agent wrote it.
Figure 1 — The four-tier answer cascade. Three free sources are exhausted before a single API call is made.
There is no database. Memory is four JSON stores on disk, which is the right shape for a single-user agent that must be readable, hand-correctable and diffable in Git. The relationship that matters is the loop: question_history is distilled into question_cache, so the agent's own past is what makes its future cheap.
%%{init: {'theme':'base','themeVariables':{'fontFamily':'-apple-system,Segoe UI,Roboto,sans-serif','fontSize':'13px','primaryColor':'#f4f1e8','primaryTextColor':'#20303f','primaryBorderColor':'#16324f','lineColor':'#16324f'}}}%%
erDiagram
PROFILE ||--o{ QUESTION_CACHE : "learns into"
PROFILE ||--o{ QUESTION_OVERRIDES : "corrects with"
PROFILE ||--o{ RELEVANCE_FILTER : "filters by"
PROFILE ||--o{ RESUME_SELECTION : "scores with"
QUESTION_HISTORY ||--o{ QUESTION_CACHE : "promoted after 3x"
APPLICATIONS_LOG ||--o{ QUESTION_HISTORY : "produces"
APPLICATIONS_LOG ||--o{ RUN_HISTORY : "summarised into"
PROFILE {
object personal "identity, location"
object experience "roles, years"
list important_boundaries "17 honesty rules"
object known_languages "confirmed only"
int salary_expectation_default
}
QUESTION_CACHE {
string question_key PK
string answer
string tier "fact · template"
string source "auto-cached after N"
}
QUESTION_OVERRIDES {
string match_key PK
string correct_answer
}
RELEVANCE_FILTER {
list relevant_keywords "310"
list irrelevant_keywords "288"
}
RESUME_SELECTION {
string category PK "ai · business · tech"
list keywords
list description_signals
}
APPLICATIONS_LOG {
string job_id PK
string title
string company
string status "applied · skipped · failed"
string application_strategy
string strategy_reason
string resume_used
int resume_score
list fit_risk_flags
list description_signals
}
QUESTION_HISTORY {
string question
string answer
string source "pre-filled · cached · claude"
string job
string timestamp
}
RUN_HISTORY {
string timestamp PK
int applied
int skipped
float cost
int api_calls
int cached_answers_used
object yield
}
Figure 2 — The four local stores and the learning loop between them.