Personal project · System & data model

SEEK Agent

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.

Python Playwright Claude API JSON local memory 202 tests
01

In plain English — the whole journey

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.

1 You tell it what to look for

You give it a kind of role — "AI operations", "IT support" — and it searches the job site the way you would.

2 It throws most listings away, for free

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.

3 It reads the ad only when the title is unclear

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.

4 It picks the right CV

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.

5 It answers the employer's questions

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.

6 It checks its own answers before it submits

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.

7 It applies, or it explains why it didn't

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.

8 It learns from what it just did

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

02

What keeps it honest — in plain words

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.

It refuses to claim credentials

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".

It rounds down, never up

On any years-of-experience question the agent takes the lower bracket. Two years of IT support is answered as two, not "2–5".

It distrusts the form's memory

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.

It won't reframe a job title

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.

It only speaks the languages it speaks

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.

It states its own confidence

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.

It explains every rejection

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.

It can be told it was wrong

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.

03

The same journey, in detail

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.

Searchone query per role, deduped by job ID
1 · Classify title5-way verdict
2 · Description gateambiguous titles only
3 · Score résumé3 candidates
4 · Answer questionscheapest source first
Strategy label + fit-risk assessment
honest answers pass
SubmitQuick Apply
Logged with reasonrésumé, score, every answer
off-target / unverifiable
Skipreason + signals recorded
Nothing is discarded silently — a skip is a logged decision, not an absence.
TWO GATES A title lands in one of five buckets: clear positive (apply), hard exclude (never — director, chief, trades, clinical), clear negative, or one of two ambiguous grades. Only ambiguous titles cost a description read. The rescue rule matters most: a seniority word like Lead or Manager is not disqualifying if the title also carries a real target signal — AI Enablement Lead survives, Lead Accountant does not.
SOFT vs HARD In the description gate, negatives are weighted by kind. Construction is a soft negative — it can't cancel explicit IT signals, so an IT Site Coordinator on a building site still passes. Clinical, 10+ years or PhD required are hard negatives and always block. This distinction was added after real listings were being dropped for the wrong reason.
04

Answering questions — cheapest source first

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.

01 Pre-filledSEEK remembers a past answer — but it may be wrong no call
↓ falls through
02 Override208 hand-written corrections for known-wrong answers no call
↓ falls through
03 Cache342 answers learned from the agent's own history no call
↓ falls through
04 Claudegenuinely new question — answered from profile facts
Honesty guard every tier · no exceptions
Allowedwritten to the form
Flaggedanswered, marked for review
Blockednever submitted

Figure 1 — The four-tier answer cascade. Three free sources are exhausted before a single API call is made.

LEARNING The cache is not hand-built — it's grown. When the same question has been answered the same way three times, that answer is promoted into permanent memory automatically. The 342-entry cache was distilled out of 1,694 logged question–answer pairs, and every promotion makes the next run cheaper. The agent converges on silence rather than spend.
05

Local memory — the data model

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
  }
PK key field one ──< many (one log produces many questions) JSON document stores, not tables

Figure 2 — The four local stores and the learning loop between them.

WHY JSON A relational store would be the reflex choice and the wrong one here. The profile is edited by hand constantly — a wrong answer is fixed by opening the file and correcting it. Keeping memory as readable documents means every override is reviewable in a diff, and the honesty rules stay legible to the person accountable for them.