Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pipelines/job-assessment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Each bullet flips from planned to done in the pull request that delivers it.

- [x] **Assessment skill.** The Claude Code skill that runs the assessment end to end.

- [ ] **Intake skill.** The interview skill that builds the central file one checked answer at a time.
- [x] **Intake skill.** The interview skill that builds the central file one checked answer at a time.

- [x] **Skills catalog and survey.** A generic skills list and an offline form for scoring yourself against it.

Expand Down
65 changes: 65 additions & 0 deletions pipelines/job-assessment/intake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Intake interview

**What it is.** A Claude Code skill that interviews you, one question at a time, and builds your `career-profile.yaml`: the one file the job assessment reads. It covers your job history, what you did at each job and where the proof of it lives, a 0 to 5 score on a list of skills, what you need from a job (pay, perks, hard limits), and the kinds of role you are looking at. The same interview also comes as plain prompts you can paste into any chat model.

**What problem it solves.** An assessment can only be as honest as the file behind it. If the file says you are strong at something you only watched others do, every verdict built on it is wrong in your favour. So the interview never writes a fact you did not say, labels where every accomplishment came from, and shows you each entry before it is saved. A skill score is kept as your own claim. It only counts as proven when it points at an accomplishment you described.

**How it was built.** Built by directing Claude Code. The author designed the interview rules and the question order, reviewed the output, and checked it against the file validator. It was not hand-typed. It is a set of instructions for a model plus the existing checked write script. There is no model training, no retrieval system, and no claim to production ML experience.

## How it works

- **`SKILL.md`** holds the rules for every stage and loads one stage file at a time.
- **`stages/00-orientation.md` to `stages/06-review.md`** hold the questions for each stage, in order, and how each answer becomes an entry.
- **`stage-prompts/00-orientation.txt` to `06-review.txt`** are the same seven stages as plain prompts, for people without Claude Code. Paste one per stage, with your current file attached.

| Stage | What it asks about | What it writes |
|---|---|---|
| 0. Orientation | where the file lives, documents to read first, your name, target roles, years in your field | the new file, `person` |
| 1. Job history | each employer, title and dates | `employers` |
| 2. Accomplishments | 2 to 5 things per employer, who did the work, what shows it happened | `evidence`, each with a source label |
| 3. Skills survey | a 0 to 5 score per skill, by offline form or in chat | `skills` |
| 4. Needs | hard limits, pay, perks, time off, wary phrases, work to avoid | `hard_blocks`, `comp`, `culture` and related lists |
| 5. Lanes | each kind of role and its own must-haves | `lanes` |
| 6. Review | the validator, a gap list, one question per gap, a closing question | `meta` |

**The rules it keeps.** One question at a time. Read any document you offer before asking what it already says. Never suggest an answer, round up a number or give you more credit than you claimed. Show the exact entry and its source label, and write it only after you say yes. Fix a contradicted entry instead of keeping two. Record each finished stage so a later session picks up where you stopped.

**One way to write.** Every answer is saved by `scripts/add_entry.py`. It checks the entry, refuses duplicates and dishonest labels (an interview answer marked as checked, for example), writes the file, and runs the full validator. The model never edits the file directly. Without shell access, the model shows the command and you run it. The two exceptions are copying the empty template at the start, and the skills form, whose saved file is merged by `intake/scripts/merge_survey.py`, a second checked script that refuses the whole merge if any answer is invalid.

## Requirements

- Python 3.10 or later with the packages in `../requirements.txt` (`pyyaml`, `jsonschema`, `pytest`).
- Claude Code for the skill, or any chat model for the plain prompts.
- Time for about 60 skill questions if you do the skills survey in chat. The offline form is quicker.

Run everything from `pipelines/job-assessment/`. To start, open Claude Code there and say "interview me for my career profile", or copy `intake/templates/career-profile.template.yaml` and paste `stage-prompts/00-orientation.txt` into your chat model.

## How it was verified

- The answers in `../fixtures/robin-sample/intake-answers.txt` (an invented person) were turned into entries the way the stage files describe and written one at a time through `scripts/add_entry.py`, starting from the empty template. `scripts/validate_profile.py` accepted the finished file with no problems. Its warnings were the gaps stage 6 is meant to ask about: accomplishments with no dates, and must-haves with no reason, because the scripted answers never gave them.
- The same run showed the write script refusing a duplicate entry, an interview answer marked as checked, and an authorship value outside the allowed list, each without changing the file.
- Not yet done: a live run of the skill on a model with the scripted answers, saved as a transcript. That run is part of the integration step listed in the [pipeline status](../README.md#status), together with the tests for the prompts below.

What this does not prove: that the questions find everything worth saying about a career, or that a model will follow every rule on every run. The write script and the validator catch the mistakes that can be checked by code. The rest depends on the model and on you reading each entry before you say yes.

---

### Prompt for your AI model

Paste one of these into any AI model, together with the files it names.

<!-- untested: these two blocks follow the design text (prompt 2 starts at stage 0, not stage 1, because the person section is required) and still need their model runs and grading before the pipeline is marked done -->

**Build your own file** (not yet tested on a model)

<!-- prompt: prompts/02-customize.txt -->
```text
I'm attaching intake/SKILL.md and intake/templates/career-profile.template.yaml. Interview me to build my own career-profile.yaml. Ask exactly one question at a time and wait for my answer. If a question has two parts, ask them separately. Never suggest an answer and never invent a fact, date, number or skill. After each answer, show the exact YAML entry you would add, with its source label, and ask me to confirm it. Anything I cannot point to a document, link or artifact for gets proof: unchecked. Start with stage 0, orientation. A good session ends with a file that passes scripts/validate_profile.py.
```

**Find the gaps in your file** (not yet tested on a model)

<!-- prompt: prompts/05-find-gaps.txt -->
```text
I'm attaching the 'What the validator checks' section of ARCHITECTURE.md and my career-profile.yaml. Find the weak spots the validator cannot catch: skills scored 3 or higher with no evidence ids, unchecked evidence a posting would lean on, evidence with no dates, must-haves with no reason, and lanes whose requirements look copied from each other. For each, give the exact YAML path, why it matters for scoring, and one question you would ask me to fix it. Do not fill any gap yourself. A good answer on fixtures/gappy-profile.yaml finds all three planted gaps.
```
221 changes: 221 additions & 0 deletions pipelines/job-assessment/intake/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
name: career-intake
description: >
Interview the user, one question at a time, to build their career-profile.yaml:
job history, accomplishments with a source for every one, a skills self-score,
what they need from a job, and the kinds of role they are looking at. Every
answer is read back as the exact YAML entry and written only after the user
says yes, and only through scripts/add_entry.py. Never invents, rounds up or
upgrades a claim. Resumes at the next unfinished stage. Use when the user
wants to build or extend the central file for the job assessment, says
"interview me for my career profile", or runs "/career-intake".
argument-hint: "[path to career-profile.yaml]"
allowed-tools: [Read, Bash, Glob]
---

# Career intake interview

This skill builds the one file the job assessment reads: `career-profile.yaml`.
It does that by interviewing the user. The file is only as honest as the
interview, so the rules below are not style advice. They are the job.

**The core rule: the user is the only source of facts.** You ask, you listen,
you write down what was said, and you show it back before it is saved. You never
supply a fact, a date, a number, a skill or a better word for what the user did.

All paths below are relative to `pipelines/job-assessment/`. Run commands from
that folder.

---

## How the interview runs

The interview has seven stages, 0 to 6. Each stage has its own file in
`stages/`. Load **only** the file for the stage you are in, and load it when
you reach that stage. Do not read ahead.

| Stage | File | Writes |
|---|---|---|
| 0. Orientation | `stages/00-orientation.md` | the new file, then `person` |
| 1. Job history | `stages/01-job-history.md` | `employers[]` |
| 2. Accomplishments | `stages/02-accomplishments.md` | `evidence[]` |
| 3. Skills survey | `stages/03-skills.md` | `skills[]` |
| 4. Needs | `stages/04-needs.md` | `hard_blocks`, `named_exceptions`, `comp`, `culture`, `soft_flags`, `benefits_and_terms`, `company_criteria`, `person.location_label`, `person.working_style` |
| 5. Lanes | `stages/05-lanes.md` | `lanes[]` |
| 6. Review | `stages/06-review.md` | `meta` |

**Starting or resuming.** If the user names a file that already exists, read it
and look at `meta.intake.stages_done`. Start at the lowest stage number that is
not in that list. Tell the user in one sentence which stage that is and why
("Stages 1 and 2 are saved, so we start at stage 3, the skills survey."). If no
file exists yet, start at stage 0.

**Users without Claude Code** can run the same interview in any chat model with
the plain prompts in `stage-prompts/`, one file per stage.

---

## Rules that apply to every stage

These seven rules hold in every stage file. A stage file can add detail. It can
never relax a rule.

### 1. One question at a time

Ask one question, then stop and wait for the answer. A question with two asks
in it is two questions: ask the first, wait, then ask the second. This holds
for follow-ups too. Never put a numbered list of questions in one message.

### 2. Do the reading first

If the user offers a r茅sum茅, portfolio link, old job description or any other
document, read it **before** asking anything it could answer. Turn what it says
into candidate entries, then confirm them one at a time ("Your r茅sum茅 says you
were Technical Writer at Northwind Example Co. from 2017-02 to 2021-12. Is that
right?") instead of asking the user to retype them.

Candidate entries taken from a r茅sum茅 get `source.type: document` and
`proof: unchecked`. A r茅sum茅 is the user's own summary of their work, so reading
it proves only that the r茅sum茅 says so. `proof: checked` is for a document, link
or artifact that is the work itself, or a record of it, and only after you have
read the passage it rests on.

### 3. Never put words in the user's mouth

- No suggested answers. Ask the question as written and wait.
- No drafted accomplishments. The claim is the user's sentence, trimmed only of
filler. Do not add adjectives, results, numbers or scope they did not say.
- No rounded-up numbers. "About 40" stays "about 40". "I don't know" stays
unknown.
- No upgraded authorship. If the user says they directed the work, it is
`DIRECTED`, never `WROTE`. If they reviewed it, it is `REVIEWED`.
- No inferred skills. A skill enters the file only when the user scores it.
- "I don't know" or "I'd rather not say" is stored as a gap (the field is left
out or `null`), never filled with a guess. Stage 6 lists every gap.

A starter menu is allowed only where a stage file says so (the hard-block
question). A menu lists options. It never pre-selects one.

### 4. Read back, then write

After each answer, show the exact YAML entry you will write, including its
source label, and ask: **"Is this right?"** Only a clear yes writes it. Any
other answer means: fix the entry, show it again, ask again.

Write as soon as the confirmed answers make a complete entry (the schema's
required fields are present). Each later answer about the same entry is read
back the same way and written with `--replace`, which swaps the stored entry
for the corrected one.

### 5. One checked door

Every write goes through `scripts/add_entry.py`. Never edit
`career-profile.yaml` by hand, with an editor tool, with `sed`, or by writing
the whole file. The one exception is stage 0, which copies the empty template
into place before the first answer exists.

```bash
python3 scripts/add_entry.py PROFILE --section evidence --entry - <<'YAML'
id: ev-northwind-api-rebuild
employer_id: northwind
claim: Moved the API reference from hand-edited pages to pages generated from the API spec.
authorship: DIRECTED
proof: unchecked
source: {type: interview, ref: "interview:2026-10-01:s2.q1", captured_on: "2026-10-01"}
YAML
```

What the door does: it checks the entry against the schema for that section,
refuses an id that is already stored (or, with `--replace`, refuses an id that
is not stored), refuses contact details and a `checked` interview entry, writes
the file, then runs the full validator and prints the result.

- **Exit 0** means written. Read the validator lines it printed. During the
interview some problems are expected (a skill pointing at evidence not yet
written). Note them; stage 6 clears them.
- **Exit 1** means refused, and the file was not touched. Tell the user in one
plain sentence what was refused and why, fix the entry, read it back again.
Never work around a refusal.

**Sections that hold a mapping** (`person`, `comp`, `culture`,
`company_criteria`, `meta`) merge top-level keys. A key's value is replaced
whole, so to add one perk you send the full `perks` list: the stored items plus
the new one. Read the file before every mapping write so nothing stored is lost.

**A model with no shell access** shows the YAML entry and the full command in
the chat, and the user runs it and pastes back the output. The rule does not
change: the file is written only by `add_entry.py`.

### 6. Correct the record

When an answer contradicts something already stored, say so in one sentence
("Stage 1 has you starting at Placeholder Labs in 2022-01, and you just said
2021. Which is right?"), wait, then fix the stored entry with `--replace`.
Never keep both versions, and never pick one yourself.

### 7. Save after every stage

`add_entry.py` saves on every write. At the end of each stage, record the stage
in `meta.intake.stages_done` so a later session resumes at the next one. The
`intake` key is replaced whole, so send the complete list and keep any stored
`closing_answer`:

```bash
python3 scripts/add_entry.py PROFILE --section meta --entry - <<'YAML'
updated: "2026-10-01"
intake:
stages_done: [0, 1, 2]
YAML
```

Then tell the user what was saved in one or two sentences and name the next
stage.

---

## Source labels

Every evidence entry carries a `source` that says where the fact came from.
The source decides what `proof` is allowed.

| Where the fact came from | `source.type` | `ref` format | Allowed `proof` |
|---|---|---|---|
| Said in the interview | `interview` | `interview:<YYYY-MM-DD>:s<stage>.q<n>` | `unchecked` only |
| A document the user supplied | `document` | `<file name>#<section>` | `checked` once you have read the passage |
| A public page | `link` | full `https://` URL | `checked` once read |
| A repo, file or published artifact | `artifact` | repo or path | `checked` once read |
| A former colleague who could vouch | `reference` | role only, never a name or contact | `unchecked` |

- `<n>` in an interview ref counts the questions asked so far in that stage,
loops included, so every ref points at one line of the transcript.
- A document you have not read yet is stored with the file name alone as `ref`
and `proof: unchecked`. Add `#<section>` and `checked` only after you read
the passage, then write the change with `--replace`.
- A link or artifact you cannot open (no network, a login wall, a dead page)
stays `unchecked`. Say so to the user. Do not mark it `checked` because the
user says it is fine.
- `captured_on` is the day the entry is written.
- `proof: do_not_use` marks something the file records but that must never be
cited, for example work the user was near but did not do. Pair it with
`authorship: OTHER-AUTHOR` when someone else did the work.

---

## What you never do

- Invent or upgrade a claim, a number, a date, an authorship mode or a skill.
- Ask two questions in one message.
- Write without a read-back and a yes.
- Write the file any way other than `scripts/add_entry.py`.
- Store an email address, phone number or a colleague's name. The file has no
contact fields, and the validator rejects contact-shaped text anywhere.
- Mark interview or reference facts `checked`.
- Keep two stored versions of the same fact.
- Score, rate or judge the user. Stage 6 lists gaps and asks; it does not grade.

## Finishing

Stage 6 ends the interview. The file is done when `scripts/validate_profile.py`
prints no problems (warnings are allowed and are listed for the user), every
gap has been asked about once, and the user's closing answer is stored in their
own words in `meta.intake.closing_answer`.
Loading
Loading