interview-prep
Skill4 starsChanged 9 days agoMITView on GitHub
Build interview packets, script live comp answers, and capture post-interview debriefs and durable lessons from a saved JD, tracker record, candidate evidence, and interview invite context.
56 other projects use a version of this.
Adopted by
Most recently taken up by
- fancyboi999/ai-engineering-from-scratch-zhtheir own version, first seen Nov 8, 2026
- rohitg00/ai-engineering-from-scratchtheir own version, first seen Nov 8, 2026
- nisarth/claude-skills-storetheir own version, first seen Nov 8, 2026
- eggs-gd/career-spacetheir own version, first seen Nov 8, 2026
- Gianni03/jobtracker-skillstheir own version, first seen Nov 8, 2026
- surfmind-space/awesome-surfmindtheir own version, first seen Nov 8, 2026
Used with
Projects with it also use application-tracker, interview-debrief and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
xiding-codeSkillapplication-tracker
Automatically log and track all job applications submitted. Use this skill after every application submission to record the details in a tracking spreadsheet and save relevant documents. Also trigger when the user asks "application status", "what have I applied to", "tracking", or wants to review their application history. Maintains a single source of truth for all job search activity.
Also in 4 projects that use this
jackhendonSkillinterview-debrief
Structure notes from a user research session into a clean debrief with insights, quotes, and implications. Paste your raw notes in and get a formatted summary ready for Notion.
Also in 3 projects that use this
proficientlyjobsSkilljob-search
Search for jobs matching my resume and preferences
Also in 9 projects that use this
claude-office-skillsSkillcover-letter
>
Also in 7 projects that use this
xiding-codeSkillcover-letter-writer
Write and iteratively refine cover letters using a 3-agent debate team (2 Opus critics + 1 Sonnet judge). Use this skill whenever a job application allows cover letter upload, whenever the user asks to write or improve a cover letter, or whenever the job-screener skill returns APPLY for a position. The must-write rule (if enabled in config) applies: if the application system has a cover letter upload field, a cover letter MUST be written. Trigger on: "write cover letter", "cover letter", any application form with a cover letter upload option, or after a job-screener APPLY decision.
Also in 3 projects that use this
claude-office-skillsSkillcompany-research
Conduct comprehensive company research and due diligence. Analyze business model, competitive landscape, management, and market position.
Also in 5 projects that use this
- +1 more from claude-office-skills
Other versions56 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 56
- rohitg00/ai-engineering-from-scratch★ 78ksame name
- anthropics/knowledge-work-plugins★ 35ksame name
- tinyfish-io/tinyfish-cookbook★ 2.3ksame name
- ShenSeanChen/waku-agent★ 2ksame name
- fancyboi999/ai-engineering-from-scratch-zh★ 1.3ksame name
- jerry-ai-dev/MODULAR-RAG-MCP-SERVER★ 1.2ksame name
- lan1177/interview-prep★ 92same name
- matyasstoch/david-skills★ 59same name
- muggl3mind/career-manager★ 24same name
- jaskirat1616/grok-skills★ 22same name
46 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository31 other files it gives its agents.
- AGENTS.mdAGENTS.md
- templates/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- templates/CLAUDE.mdCLAUDE.md
- answer-questionSkill
- apply-jobSkill
- calendar-syncSkill
- coach-gapsSkill
- company-healthSkill
- configureSkill
- discover-companiesSkill
- email-commsSkill
- evaluate-jobSkill
- ingest-mailSkill
- ingest-messagesSkill
- ingest-profileSkill
- intake-extractSkill
- optimize-linkedinSkill
- reevaluate-strategySkill
- relationship-sourcingSkill
- report-issueSkill
- research-boardsSkill
- research-companySkill
- research-compSkill
- resume-extractSkill
- schedule-meetingSkill
- search-jobsSkill
- setup-searchesSkill
- sync-statusSkill
- tailor-applicationSkill
- track-outcomesSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
63 rules. 0 are common, 34 are distinctive, and 29 are very specific to this project.
0
Common
Most of the 48 edited versions we read keep these.
34
Distinctive
This author's own choices. Worth a look.
29
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Use this skill when a submitted role advances to recruiter screen, technical screen, hiring-manager interview, panel, assessment, or offer …
- Distinctive: line 20
Read all of the following before doing anything else:
- Distinctive: line 50
Read prior lessons for this role family before building the packet — they sharpen fit signals, likely questions, and comp anchoring.
- Distinctive: line 58
Read any company research artifact before building the packet — it sharpens the Positioning Thesis and the questions-to-ask.
- Distinctive: line 71
Read the STAR+R story bank before building the packet — it holds reusable behavioural answers that already trace to evidence.
- Very specific: line 83
Run careerrat tracker --summary to confirm funnel state.
- Distinctive: line 84
Check usage mode before building a deep multi-audience packet:
- Very specific: line 90
If it returns downshift, build one lighter packet focused on the actual next round rather than recruiter + hiring-manager + panel variants.
- Very specific: line 105
If invite notes are missing or ambiguous, ask for them before proceeding.
- Very specific: line 107
When round type is offer, run STEP 3b (Live offer negotiation) immediately after STEP 3 — before STEP 4.
- Very specific: line 144
Call renderInterviewPacket() from src/core/interview/packet.mjs with:
- Distinctive: line 150
Write the rendered output to:
- Very specific: line 219
Do), then give a plain-language "why you fit" (1–2 sentences a non-technical hiring manager follows), the backing evidence.yml
- Distinctive: line 233
Likely Questions — with scripted answers: Every likely question MUST carry a
- Very specific: line 251
When you script an answer from a banked story that the renderer did not auto-match, add that question to the story's prompts[] (re-add …
- Very specific: line 300
If no comp-bench artifact exists, omit the market-number sentence entirely and anchor on profile.compensation.target_base plus …
- Distinctive: line 308
Before placing this in the packet, obtain the real deadline date from the candidate and substitute it for the date slot:
- Distinctive: line 342
Every 🔴 behavioural-gap question from STEP 2 section 5 lands here.
- Distinctive: line 370
Write the draft to a temp YAML file and propose it (dry run):
- Distinctive: line 400
Never fabricate a story to fill a gap.
- Distinctive: line 407
When the round is a technical screen, system-design, data-architecture, or any "whiteboard" / "design" round, the standard packet sections …
- Distinctive: line 448
Keep only the handful worth a simple phrasing — a memory jog, or a script for when a *less-technical* interviewer asks "explain that …
- Distinctive: line 463
When the candidate wants to *rehearse* rather than read — "mock me," "run a mock interview," "quiz me on this round," "let's practice" — …
- Very specific: line 468
Role-play the interviewer for the specific round: Use the saved JD, the round format from conversations[].processNote, and the named …
- Very specific: line 490
Read candidate config via the shared DB-first accessor (profile.compensation.minimum_base) — the single walk-away floor.
- Distinctive: line 492
If the posted band is at or above the floor: no action needed.
- Distinctive: line 494
If the posted band is below the floor: include a walk-away script in the Comp and Logistics section and flag the gap to the user.
- Distinctive: line 497
If the user states a new comp gate mid-session (e.g.
- Very specific: line 500
Run careerrat gate comp-floor <N> --write --confirm, then echo the CLI confirmation.
- Distinctive: line 510
The call-time hold rule: Never accept or commit to a number on the call.
- Distinctive: line 517
Pre-call checklist: Before the offer call, confirm these inputs are loaded:
- Very specific: line 540
Concession ladder: When base is constrained, offer to trade in this order (adjust for profile.compensation.cash_over_equity):
- Distinctive: line 545
Start date (later start = more runway at current income)
- Very specific: line 550
Never trade base below minimum_base (or oe_min_base).
- Distinctive: line 553
Geographic-discount rebuttal (verbal form): If the employer cites candidate location or remote status to justify a lower number:
- Very specific: line 556
Use this rebuttal only when a workspace/research/comp-bench-*.md artifact exists for this role.
- Distinctive: line 565
BATNA use on the call: Use only when a real competing offer or genuine alternative exists.
- Distinctive: line 570
Before placing any BATNA script in the packet, obtain the real deadline date from the candidate.
- Distinctive: line 576
If the employer cannot move by the deadline: honour the deadline.
- Very specific: line 587
After a walk-away, close the app row immediately — do not defer to a later STEP 5 pass.
- Very specific: line 597
Run careerrat tracker --verify immediately after.
- Distinctive: line 599
After the call — hand off to email-comms: Every substantive verbal exchange (offer received, counter indicated, concession offered) must be …
- Very specific: line 604
Write the comm thread state in the same tracker.json edit as the call summary — do not leave the comm record in its pre-call state:
- Distinctive: line 638
After lint passes clean (STEP 4), render the styled PDF — the only export the packet ships:
- Distinctive: line 657
Write a debrief file to:
- Very specific: line 691
Outcome write-back — same write as conversations[] append: After writing the debrief file and appending conversations[], also update these …
- Very specific: line 716
When the debrief surfaces a detail that would strengthen a story but you don't have yet (a number to confirm, a concrete before/after, a …
- Very specific: line 722
Recording: If the user consented to recording, set recording to the source (e.g.
- Very specific: line 732
Mode detection: run careerrat data status.
- Very specific: line 737
DB workspace: never hand-edit workspace/tracker.json or workspace/activity.jsonl.
- Distinctive: line 751
When setting a confirmed future interview datetime and the existing value is absent or in the past, use the booking-aware verb first:
- Distinctive: line 764
When the prep or debrief changes pipeline status, use:
- Distinctive: line 773
When STEP 3b offer-call handling also resolves a communication thread, patch the full communication row and persist it with:
- Distinctive: line 780
After DB writes, run:
- Very specific: line 787
Run careerrat tracker afterward only when a recovery snapshot is useful.
- Very specific: line 853
Stage write-back when a confirmed interview date is known: When the invite has a confirmed date, write all of the following in the same …
- Very specific: line 863
Legacy workspace (no DB): after editing tracker.json, run in sequence:
- Distinctive: line 895
Keep it honest — never invent a lesson that didn't happen.
- Very specific: line 897
Write the composed body to a temp file, then dry-run (lint) and commit via the learnings CLI (the helper creates candidate/learnings/ and …
- Very specific: line 909
Never include current_base or "currently make" language in the entry body — the CLI will refuse it.
- Very specific: line 918
Use
candidate/evidence.ymlverbatim: Never invent stories, metrics, or claims. - Very specific: line 920
Select across the WHOLE evidence bank, ranked by relevance: When building Top Fit Signals and the JD Fit Map, consider every claim in …
- Distinctive: line 972
Plain language, no buzzword chains: Write every section so a smart non-specialist hiring manager follows it.
“Common” is measured: kept by at least 6 in 10 of the 48 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
interview-prep
Use this skill when a submitted role advances to recruiter screen, technical screen, hiring-manager interview, panel, assessment, or offer conversation. Also use it when the user pastes interview notes, a transcript, or a debrief.
Runs under AGENTS.md. These contracts bind without being restated here: Privacy Invariant (
current_basenever outbound), Honesty Firewall, Placeholder/Bracket Ban, Gate Write-back, Domain-Neutral Rule, Browser Automation Contract, Activity Pulse logging, Tracker verify+snapshot, and Sent-Clears-Draft. Inline reminders at point-of-use are intentional; standalone restatements point back to the relevant AGENTS.md section. Barecandidate/,workspace/,config/, and.internal/paths below are symbolic; resolve them per AGENTS.md's Path Resolution rule.
Agent voice. Read
candidate/modes.yml#agent_voice(defaultstandard) before producing packet summaries, prep briefs, or debrief summaries. Apply the register from AGENTS.md#mode-switches.exec-summary= verdict + 2–3 bullet signals;standard= scannable packet with section headers;technical= full signal analysis + coaching depth;verbose= everything including full question lists and story texts.
STEP 0 — Read context
Read all of the following before doing anything else:
workspace/tracker.json— find the application row for this company/role. Notestatus,conversations[],artifacts.jd,fitScore,note. Mine every priorconversations[]entry on this row for what THIS company has told you they want. TheprocessNoteandlearnings[]fields capture stated priorities, "big bets," what each interviewer emphasized, and objections heard in earlier rounds. This is the richest, most company-specific signal you have, richer than the JD, because it came straight from their mouths. Carry it forward: the packet must anchor its examples and answers to these stated priorities, not just generic fit. Prep compounds round over round, every round's debrief (STEP 5/6) is meant to make the next round's packet sharper.candidate/profile.yml— readcompensation.target_base,compensation.minimum_base,compensation.expected_base,compensation.current_comp_shareable.current_baseis private — never read it into any outbound section.candidate/targeting.yml— readrole_buckets(for role-family fallback),role_families(if present, for classifyRoleFamily override). Also noteprofile.location.travel_tolerance(incandidate/profile.yml) for the travel/onsite burden used in comp evaluation. (lifestyle_burdenis a planned field not yet in the schema — usetravel_tolerancein its place.)candidate/evidence.yml— evidence claims for the JD signal match and do-not-overclaim sections.candidate/honesty.yml—tools.do_not_claim,claims.do_not_fabricate, education policy.- Derive the role family by calling
classifyRoleFamily(role)fromsrc/core/tracker/outcome-analysis.mjs. If it returns'other', use the first matchingrole_buckets[].namefromcandidate/targeting.ymlas the family key (e.g.dispatcher,nurse,driver). If no bucket matches, leave the family asother. Read prior lessons for this role family before building the packet — they sharpen fit signals, likely questions, and comp anchoring. Run:
careerrat learnings read "<role title or family>"A missing file is normal — the CLI prints a skip note to stderr and exits 0.
Read any company research artifact before building the packet — it sharpens the Positioning Thesis and the questions-to-ask. Run:
careerrat research read "<company>"A missing file is normal (skip note to stderr, exit 0). If present, fold its sourced signals into the Positioning Thesis and Questions To Ask as context to assess — never as evidence-backed claims. Treat anything marked
[AGENT-INFERRED ...]as a hypothesis for the candidate to verify, not a fact to assert. If the artifact is flagged stale, note it and consider re-runningresearch-company. Never copy a research finding into an evidence/résumé claim.Read the STAR+R story bank before building the packet — it holds reusable behavioural answers that already trace to evidence. Run:
careerrat stories match "workspace/jobs/<jd-file>.md"(or
--signals "a,b,c"when you don't have the JD JSON path). A missing or empty bank is normal. The matched stories become the packet's Prepared Stories section (STEP 2); uncovered behavioural themes (careerrat stories gaps) are candidates to draft in STEP 2b. Never treat a story as more than itsevidence_idsback.- Run
careerrat tracker --summaryto confirm funnel state. - Check usage mode before building a deep multi-audience packet:
careerrat modes allows interview:packet:deepIf it returns downshift, build one lighter packet focused on the actual next
round rather than recruiter + hiring-manager + panel variants. If it returns
run, proceed normally. Usage mode never changes honesty/evidence requirements.
STEP 1 — Identify audience and round
Extract from the invite notes or user input:
- Round type:
recruiter screen | assessment | technical | hiring manager | onsite | final | offer - Interviewer name(s) and title(s) for each slot (track separately — prep differs by audience).
- Logistics: date/time/timezone, format (video/phone/on-site), duration.
If invite notes are missing or ambiguous, ask for them before proceeding.
Record the round type — it drives which sections of the packet expand.
When round type is offer, run STEP 3b (Live offer negotiation)
immediately after STEP 3 — before STEP 4.
STEP 2 — Build and write the packet
There is no CLI wrapper for renderInterviewPacket(). Invoke it via a
one-off node script:
// run as: node --input-type=module < /dev/stdin
import { renderInterviewPacket } from './src/core/interview/packet.mjs';
import { loadStories } from './src/core/interview/story-bank.mjs';
import { parseYaml } from './src/core/profile/yaml.mjs';
import { parseSavedJob } from './src/core/evaluate/gate.mjs';
import { readFileSync } from 'node:fs';
const job = parseSavedJob(readFileSync('workspace/jobs/<jd-file>.md', 'utf8'));
const profile = parseYaml(readFileSync('candidate/profile.yml', 'utf8'));
const evidence = parseYaml(readFileSync('candidate/evidence.yml', 'utf8'));
const honesty = parseYaml(readFileSync('candidate/honesty.yml', 'utf8'));
const { stories } = loadStories({}); // candidate/stories.yml — [] when absent
const out = renderInterviewPacket({ job, profile, evidence, honesty, inviteNotes: '', audience: '<round-type>', stories });
process.stdout.write(out);Or write the packet body inline in the agent response using the section structure below, reading all inputs directly — whichever path is available.
Privacy note: packet.mjs does not emit current_base under any
condition — buildCompAndLogistics() only ever outputs expected_base,
target_base, and minimum_base. No stripping step is needed.
Artifact privacy: the packet is a candidate-private prep artifact — it
includes minimum_base (the walk-away floor). Do not hand it to a panel or
interviewer; save it only to workspace/interview-prep/.
Call renderInterviewPacket() from src/core/interview/packet.mjs with:
{ job, profile, evidence, honesty, application, inviteNotes, audience }Write the rendered output to:
workspace/interview-prep/<company>-<role>.md(Use the slug from the tracker row. Create workspace/interview-prep/ if
absent.)
This .md is also the source for the dashboard Focus-card "Open dossier"
full-page preview — STEP 6 persists a copy onto app.artifacts.interviewDossier
so the featured interview always has a dossier to open.
In-interview usability contract (hard). A packet is read in two modes, and the live mode wins. The candidate has this open during the call — build for that first:
- Live layer (the cue card) — the priority, and it goes first. One screen, glanceable mid-sentence: cues, keywords, triggers, and the hard numbers — never paragraphs. A story is a 3–5 word trigger + its one metric, not a script. Questions- to-ask are bare lines. Do-not-claim is a short list. The test: can the candidate find and use any item in the ~2 seconds they have while still talking? If using it means reading a paragraph, it fails the test and does not belong on the live layer.
- Study layer (everything else) — pre-read only, and labeled as such. The Positioning Thesis, JD Fit Map, and fuller answers exist to internalize before the call. They are never the thing referenced live.
- No walls of text, no jargon, no speeches. A blockquoted verbatim monologue cannot be read aloud while holding a conversation, and reads as canned if attempted. Scripted answers (section 5) are a rehearsal aid in the study layer; on the live card each one collapses to its trigger + number. Plain labels over jargon throughout. Scannable beats complete — a wall of correct bullets is study material, not a live reference.
The packet must contain these sections in order:
Lead-off — Say-This / Never-Say-This Card. The front page of the packet and
the one part meant to be memorized, not studied. Keep it to a single screen: the
3–5 highest-leverage things to say close to verbatim in this round, and the
traps to never say. This exists because the most common failure mode is not
missing prep — it is having the right answer somewhere in the packet and failing
to retrieve it under pressure. Pull each "say" line from the strongest backing in
candidate/evidence.yml:
- Impact / adoption / "how do you measure success" answers must state the actual metric from the backing claim (deflection rate, dollars saved, time cut, count served) — never a qualitative stand-in ("it's taking off," "people love it") when a real number exists in the evidence bank.
- The "can you do X / what are your weaknesses" answers are scripted as a constructive reframe — what the candidate does ship and how — followed by a calm, factual boundary, never a self-deprecating disclaimer. The numbered sections below are the support; this card is what survives the room.
- Positioning Thesis — one-paragraph framing of why this candidate for this role at this company. Sourced from evidence; no invented claims.
- Top Fit Signals — 3–5 signal/evidence pairs drawn from JD language and
candidate/evidence.yml. Ifjob.signalsis empty (sparse JD or non-tech domain), derive signals from JD body keywords androle_buckets.notesintargeting.ymlrather than leaving the table blank. - JD Fit Map — the heart of the packet, and the section the candidate reads
most. Ground it in the candidate-facing JD's actual words, not abstract
signal tokens:
- Open with a plain read of the role — 2–4 bullets that strip the buzzwords and say what the job actually is ("build AI products customers use; build internal AI tools; take on a few big bets"). The candidate should understand the role before seeing the mapping.
- Fit Snapshot table — scannable: quoted ask (their words) → one-line plain-language fit → strength (strong / partial / gap).
- Per-line expansion — only the 3–4 highest-leverage lines. The snapshot
table already covers everything; in prose, expand just the few lines that win
or lose the round (the candidate's edge, a gap that just closed, the honest
partials). Quote the JD's real line (from
## Requirements/## What You'll Do), then give a plain-language "why you fit" (1–2 sentences a non-technical hiring manager follows), the backingevidence.ymlclaim id(s), and an honest confidence (strong / partial / gap). Do not expand every line — a wall of fit bullets is study material, not something usable in a 30–45 minute call. Mark partials and gaps explicitly — never round a partial up to strong; the honest "here's where I'm light" framing is the value. Thejob.signals[]tokens are a matching aid for finding the right evidence — they are not what you show the candidate. Translate every match into the JD's own language and the candidate's real work.
- Prepared Stories (STAR+R) — matched stories from
candidate/stories.yml(careerrat stories match), rendered Situation/Task/Action/Result/Reflection. These are the answer anchors for the Likely Questions below. Omitted when the bank is empty. Each story already traces to evidence — use it as written; never improvise past itsevidence_ids. - Likely Questions — with scripted answers. Every likely question MUST carry a
scripted, say-it-verbatim answer, not just the question text. A bare question
list is not done — the most common failure is having the right answer somewhere in
the candidate's history and not retrieving it under pressure, so write the words.
These verbatim answers are study-layer material (see the In-interview usability
contract): for pre-call rehearsal only — on the live cue card each collapses to its
trigger + metric, never the full script.
The renderer scaffolds this for you:
buildLikelyQuestionsSection()attaches each question's anchor automatically — a banked story (matched via the story'sprompts[]), the Positioning Thesis (for the opener), the JD Signal Match row (for "experience with<signal>"), or a 🔴 behavioural-gap flag when nothing backs it. For each question:- Label
[JD-inferred]or[evidence-sourced], and keep the anchor line. - Write the verbatim answer under the anchor — anchored to a Prepared Story (by title) when one fits, else to specific evidence claims by name. For any impact / adoption / "how do you measure success" question, the answer must carry the verbatim metric from the backing claim, not a paraphrase — these are the questions candidates most often answer qualitatively and lose.
- When you script an answer from a banked story that the renderer did not
auto-match, add that question to the story's
prompts[](re-add --write) so it auto-anchors next loop. - For every 🔴 behavioural-gap question, do NOT improvise an answer. Route it to the seed-a-story flow (STEP 2b): surface two evidence-grounded example angles to jog the candidate's memory, ask them for the real anecdote, then draft + bank a STAR+R story and replace the flag with the scripted answer. A behavioural answer you can't trace to evidence is a question for the candidate, never a packet entry.
- Label
- Questions To Ask — 4–6 questions tailored to audience (recruiter set ≠ HM set ≠ panel set). Expand this section when round is HM or later.
Comp and Logistics Script — use
profile.compensation.target_baseas the opening anchor;minimum_baseas the private walk-away. Never includecurrent_basein this section regardless ofcurrent_comp_shareable. Script a live answer for "what are your expectations?". If the posted band is belowprofile.compensation.minimum_base, note the gap and include a walk-away script.Live verbal scripts (offer round) — strategy from AGENTS.md Negotiation Contract; mechanics below. Include these scripts in the packet whenever
audienceisoffer.Privacy deflection — "what are you currently making?"
Deliver the following (substitute the candidate's
profile.compensation.target_basevalue for the dollar figure):"I keep current comp private, but I can tell you what I'm targeting: $X. That reflects what comparable roles are paying in the market and what makes this move compelling for me."
Opening anchor — "what are your expectations?"
Substitute the candidate's
profile.compensation.target_basevalue and one impact claim fromcandidate/evidence.ymlbefore placing this in the packet:"I'm targeting $X. That's grounded in market data for this scope and my track record of delivering
<a specific impact from evidence.yml>."For OE roles substitute
profile.compensation.oe_max_baseas the anchor;oe_min_baseis the walk-away (internal — never spoken). Never citeminimum_base/oe_min_baseoutbound.Geographic-discount pushback — if the employer cites candidate location or remote status to justify a lower number, use this script only when a
workspace/research/comp-bench-*.mdartifact exists for this role. Pullbenchmark.midpointfrom that artifact's frontmatter. Do not cite any market number that was not actually benchmarked.Substitute the
benchmark.midpointvalue from the comp-bench artifact before placing this in the packet:"The market data I have puts the midpoint for this scope at $X, measuring the role nationally for fully-remote positions. I'd rather anchor there. Let me put a written counter together so you have the full picture — can I send that over by tomorrow?"
If no comp-bench artifact exists, omit the market-number sentence entirely and anchor on
profile.compensation.target_baseplus evidence-backed value only.BATNA script — use only when the candidate has a real competing offer or genuine alternative. Never fabricate an offer, deadline, or number. If there is no BATNA, omit this script block entirely from the packet and note plainly: "No BATNA — using market anchor + evidence-backed value approach."
Before placing this in the packet, obtain the real deadline date from the candidate and substitute it for the date slot:
"I do have another offer in process — I'm not trying to use it as pressure, but it is affecting my timeline. I need to respond to them by
<the date the candidate confirms>. I'd genuinely prefer this role, which is why I want to make sure we can get to $X before I have to decide."Substitute
profile.compensation.target_basefor $X.Multi-round verbal sequencing and the call-time hold rule — see STEP 3b for the full round-by-round mechanics. The single most important rule: do not accept or commit to a number on the call. Acknowledge, then bridge to a written counter via
email-comms.- Do Not Overclaim — verbatim from
honesty.yml: tools not to claim, claims not to fabricate, education boundary. Segment by audience when known. Pair each boundary with its constructive reframe — the honest strength to lead with before stating the limit — so ado_not_claimline is delivered as "here's what I do bring, and here's the edge of it," never as a bare, self-deprecating disclaimer. - Evidence Gaps — signals the JD requires that have no backing claim in
evidence.yml. Surface these so the user can add them before the interview. - Sub-floor fit note (when applicable). If the tracker row's
fitScoreis below the configuredtargeting.fit_bands.fit_floor— meaning this role would normally auto-drop but the candidate is in the loop anyway (inbound recruiter interest, referral, etc.) — surface it explicitly here: "This role scored<N>, which is below your fit floor of <fit_floor>. You're in the process regardless, so prep the specific gaps that drove the low score." Pull the gap signals from section 9 (Evidence Gaps) and the FIT caveats from the evaluate-job output. Omit this note when nofit_flooris configured or when the score meets the floor.
STEP 2b — Draft and bank STAR+R stories (close coverage gaps)
The story bank compounds across loops — build it as you prep, don't restart each round. After writing the packet, check behavioural coverage:
careerrat stories gapsEvery 🔴 behavioural-gap question from STEP 2 section 5 lands here. Surface gaps to the candidate with memory-joggers — do not leave them as a silent to-do, and never fabricate a story to fill one.
For each uncovered competency (or a Likely Question with no Prepared Story):
- Ask the candidate, with two example angles to jog their memory. Don't ask a bare
"tell me about a time you…" — the candidate often has the story but can't retrieve it
cold. For each gap, scan
candidate/evidence.ymlfor the 1–2 closest real projects and offer two concrete example angles grounded in that evidence ("Was it like X — driving Pearl adoption with no mandate? Or like Y — making the build-vs-buy case to leadership?"), then ask which is closest or for the real one. UseAskUserQuestionwith the two angles as options (each a short evidence-grounded sketch) so the candidate can pick the closest or describe their own. This is the step that turns a gap into a bankable story. - Find the backing claim(s) in
candidate/evidence.ymlfor the angle the candidate confirms. If no claim backs it, it is an Evidence Gap (STEP 2 section 9) — surface it for the candidate to fill; do not invent a story to cover it. If the candidate has built relevant projects that simply aren't in evidence yet, offer to scan them (ingest-profileSTEP 2b,careerrat evidence add) to originate the claims first, then draft the story. New biographical detail the candidate gives you on the call (an origin, an outcome) is their real account — fold it into the narrative even when it isn't yet a separate evidence claim, but keepevidence_idspointed at claims that exist. - Draft a STAR+R story from those claims: Situation/Task/Action/Result from the
claim's
evidence+metrics, Reflection in the candidate's voice. Use the candidate's real details; go generic only on specifics you genuinely don't have (never bracket placeholders). List every claim id used inevidence_ids. Write the draft to a temp YAML file and propose it (dry run):
careerrat stories add --file <draft.yml>The firewall refuses a story that cites no or unknown evidence, drops a STAR+R field, carries placeholder residue, or leaks comp. On the candidate's confirmation, commit (atomic upsert by id):
careerrat stories add --file <draft.yml> --write- Set
open_questions[]for anything the story is still missing — don't block on it. The candidate's account is truth: bank the story now, then list the thin spots (a metric to confirm, a concrete before/after, a version question, a detail that conflicts with committed code) asopen_questionsstrings on the story. This is the "ingest sets the flag" rule — banking a story that lacks a detail must record that gap, never silently drop it or gate the bank on it.stories add --writeauto-mirrorsopen_questionsintotracker.storyEnrichment, which raises a self-clearing "give me more context" card in the dashboard Next Steps queue (one per thin story, "Give context" action). When the candidate fills a gap, drop that string fromopen_questionsand re-add --write; the card clears on the next sync. (If you editstories.ymloutside the CLI, runcareerrat stories sync-enrichment --writeto refresh the mirror.) Frame eachopen_questionsentry as the actionable ask you'd put to the candidate, not an accusation — the candidate's account is the source of truth; the gap is just what would sharpen it.
Never fabricate a story to fill a gap. A behavioural answer you can't trace to evidence is a question for the candidate, not a packet entry.
STEP 2c — Technical / system-design rounds (diagrams, the why, and probes)
When the round is a technical screen, system-design, data-architecture, or any "whiteboard" / "design" round, the standard packet sections are not enough on their own. A talking-point list is not prep for a design round. The candidate has to walk in able to draw something and defend every box. Add:
- A "What they're scoring" rubric at the very top, decoded from the round's own description (e.g. "can you justify every choice", "do you find the bottleneck", "do you design for failure", "do you communicate while building"). The candidate should see the grading criteria before anything else.
- A reusable frame they can run on any prompt: scope/users → rough scale numbers → components + request flow → data model + access → bottleneck + trade-off → failure modes. Plus the opening line that buys time to scope.
- Worked design walk-throughs — at least one grounded in the company's stated
priorities / big bets (from STEP 0 item 1), plus one or two generic ones.
Each walk-through MUST carry all three of:
- an ASCII architecture diagram in a fenced code block, simple enough to reproduce on a whiteboard;
- the reasoning behind every component choice, attached to that component — why this one, and what you gave up — never collected in a separate list far from the decision. "Postgres" with no "why" next to it is exactly the failure this step exists to prevent;
- the likely follow-up probe and the scripted answer ("if they push 'why not a dedicated vector DB' → ..."). Design interviewers always push on the choices; script the defense in advance.
- A "why" cheat sheet — each default building-block choice (datastore, queue, gateway, cache, region strategy) with a one-line defense and the common probe, so the candidate can answer "why that?" for anything they end up drawing.
- Ground every box in the candidate's real systems from
evidence.yml, so the reasoning reads as experience, not a textbook. Keep the do-not-overclaim boundaries (STEP 2 section 8) visible for this round's stack, and script the honest-edge move ("I haven't run that at that scale, but here's how I'd reason about it, and the trade-off I'd watch") so the candidate never bluffs a number. - A short plain-English glossary at the bottom — only the terms that earn it.
This is for genuinely specialized jargon where a crisp one-liner is useful:
named techniques and niche terms like top-k, RRF, reranking. It is NOT a vocab
dump. Calibrate hard to the candidate's actual level (read
evidence.yml/profile.yml): cut anything they obviously know cold — general engineering concepts they use daily (queue, idempotency, dependency, CVE, fallback, access control, blast radius), and especially anything they built themselves (if they shipped an MCP server, "MCP" does not belong in their glossary). Listing terms a senior engineer knows reads as condescending and they'll call it out. Keep only the handful worth a simple phrasing — a memory jog, or a script for when a less-technical interviewer asks "explain that simply." If the round's interviewer is a domain expert and every term is in the candidate's wheelhouse, skip the glossary entirely. One conversational line per kept term, the way they'd say it out loud.
This section is additive: for a mixed round the behavioural sections (Prepared
Stories, Likely Questions) still apply. Write the per-round doc to
workspace/interview-prep/<company>-<round-or-panel-slot>.md and export each as
its own PDF (STEP 4b) so a multi-session panel gets one focused PDF per session.
STEP 2d — Mock interview mode (agent-driven)
When the candidate wants to rehearse rather than read — "mock me," "run a mock interview," "quiz me on this round," "let's practice" — the agent drives a live mock instead of (or after) producing a doc. This is the highest-value prep for a candidate who solves in the moment rather than from memorized scripts.
- Role-play the interviewer for the specific round. Use the saved JD, the round
format from
conversations[].processNote, and the named interviewer when known (match their angle — system design vs behavioural vs AI-fluency). Ask one question at a time and wait for the real answer. Never dump a question list. - Voice is fine. The candidate can answer by dictation; keep agent turns short so it stays a conversation, not a wall. One question, one reaction, next question.
- Tight feedback per answer — one or two specifics, not an essay. Did the metric
land, did they bury the lede, did they ramble past the point, did they open with a
disclaimer instead of deriving, did they defer when they could have reasoned it out
loud. Coach the framing and retrieval, never tell them to go memorize — see how the
candidate actually works in
candidate/context and the role-family learnings. - Close by updating the prep, not just talking. Fold what surfaced into the live
cue card (STEP 2), bank durable lessons via the learnings helper (STEP 7), and capture
the rehearsal in
conversations[]only if the candidate wants it tracked. - The formalized version — book a mock as a calendar event, scored rubric, recorded, full voice loop — is on the roadmap. Until it ships, run the mock inline in the session; it works today.
STEP 3 — Comp gate check
Read candidate config via the shared DB-first accessor (profile.compensation.minimum_base) — the single walk-away floor. In DB mode this comes from SQLite; YAML is legacy fallback only.
- If the posted band is at or above the floor: no action needed; include the standard comp script (target, not current).
- If the posted band is below the floor: include a walk-away script in the Comp and Logistics section and flag the gap to the user.
- Unposted comp (estimated from comparables). If the tracker row's
compEstimate.source === "comparables"— meaning the role never had a posted band and the gate estimated it from comparable tracker rows — surface this at the top of the Comp and Logistics Script section: state the estimate range ("Our best guess from N comparable roles: $<low>K–$<high>K, mid $<mid>K; confidence<X>") and instruct the candidate to confirm the real band early in the first live conversation before anchoring on any number. We're prepping off a guess; the first screen call is the chance to ground it. If the estimate isestimated-below-floor, also flag the likely gap and include the walk-away script as a contingency. - If the user states a new comp gate mid-session (e.g. "below $190K is a no"):
- Confirm-first for lowering the floor.
- Write-and-report (applied directly, logged with an Undo) for raising it or other unambiguous per-session adjustments.
- Run
careerrat gate comp-floor <N> --write --confirm, then echo the CLI confirmation.careerrat gatewrites SQLite in DB mode and legacy YAML only in legacy mode; never hand-editcandidate/profile.yml.
STEP 3b — Live offer negotiation (verbal channel)
Run this step when the round type is offer. Strategy lives in
AGENTS.md Negotiation Contract; this step owns the verbal channel only.
The call-time hold rule. Never accept or commit to a number on the call. Acknowledge every offer warmly, then bridge to written:
"That's helpful — I want to give this the consideration it deserves. Can I send you a written response by tomorrow?"
Then trigger email-comms to draft and send the counter.
Pre-call checklist. Before the offer call, confirm these inputs are loaded:
profile.compensation.target_base— verbal anchor (OE roles:oe_max_base)profile.compensation.minimum_base— private walk-away, never spoken aloud (OE roles:oe_min_base)profile.compensation.minimum_base— the walk-away floor; trigger for the walk-away script when an offer falls below it.workspace/research/comp-bench-<role>-<loc>-<yyyy-mm>.md— frontmatterbenchmark.floor/midpoint/ceiling/confidence; cite only if the file exists on diskcandidate/evidence.yml— one or two impact claims to use as value evidence- Competing offer details if real (date, number, company type); leave blank if none — never fabricate
Round-by-round verbal sequencing.
| Round | Candidate action | Script anchor |
| Round 1 — opening anchor | State target_base with one evidence claim. Bridge to written. | "I'm targeting $X based on <one evidence claim>. Let me send a written summary tonight." Substitute values before placing in packet. |
| Round 2 — employer counter | Acknowledge; do not counter on the call. Ask what's driving the number (budget ceiling? band?). Bridge again. | "Thank you — I want to understand the constraints. Is this the top of the approved band? Let me think through it and respond in writing." |
| Round 3 — written counter sent | email-comms owns this round. Return here only if a follow-up call is requested. | |
| Round 4 — re-counter or close | If the gap is closeable, move to concession ladder. If below minimum_base / oe_min_base, execute walk-away script. |
Concession ladder. When base is constrained, offer to trade in this order
(adjust for profile.compensation.cash_over_equity):
- Sign-on bonus (one-time, does not set base precedent)
- Equity / vesting terms (when
cash_over_equityisfalseand the role includes equity) - Start date (later start = more runway at current income)
- Title (if a stronger title opens the next salary band)
- Remote / relocation terms
- Additional PTO or benefits with quantifiable value
Never trade base below minimum_base (or oe_min_base). State each concession
explicitly in the written counter via email-comms, not verbally.
Geographic-discount rebuttal (verbal form). If the employer cites candidate location or remote status to justify a lower number:
- Use this rebuttal only when a
workspace/research/comp-bench-*.mdartifact exists for this role. Pullbenchmark.midpointfrom its frontmatter. If no artifact exists, anchor ontarget_baseonly — do not invent a market number. - Substitute the
benchmark.midpointvalue before placing this in the packet:"The market data I have for this scope puts the midpoint at $X. That figure reflects the role's requirements rather than my zip code. Let me put a written response together so you have the full picture — does tomorrow work?"
- Bridge to written immediately. Do not debate on the call.
BATNA use on the call. Use only when a real competing offer or genuine alternative exists. If no BATNA: note this plainly in the packet; use market anchor + evidence + time as the only levers.
- State the BATNA once, neutrally, without pressure language.
- Before placing any BATNA script in the packet, obtain the real deadline date from the candidate. Never invent a deadline or emit a bracket token for it.
- Substitute the confirmed date before placing this in the packet:
"I have another offer I need to respond to by
<the date the candidate confirms>. I'd rather be here, which is why I want to resolve comp before that date." - If the employer cannot move by the deadline: honour the deadline; do not extend it to apply fake pressure.
Walk-away script — use when the offer falls below profile.compensation.minimum_base
(or oe_min_base).
State the floor value as a dollar figure (never say minimum_base aloud):
"I appreciate the offer and the time everyone has invested. I need a base of at least $X to move forward. If that's outside what's possible right now, I completely understand — I'd rather be transparent than waste anyone's time."
After a walk-away, close the app row immediately — do not defer to a later STEP 5 pass. Write all of the following in a single tracker.json edit:
app.status → "rejected"(employer declined to meet floor) or"withdrawn"(self-initiated)jobs[].nextAction → nulljobs[].nextActionDue → nulljobs[].followUp → null(if set)comm.status → "closed"comm.nextActionDue → null- Append a terminal
conversations[]entry:{ "kind": "offer", "notes": "Walk-away executed — offer below comp floor.", ... }
Run careerrat tracker --verify immediately after. Then write the outcome to the debrief file (STEP 5) and log lessons (STEP 7).
After the call — hand off to email-comms. Every substantive verbal exchange (offer received, counter indicated, concession offered) must be persisted:
- Trigger
email-commsto draft the written counter. The counter email is the formal negotiation record — it supersedes anything said on the call. - Write the comm thread state in the same tracker.json edit as the call summary — do not leave the comm record in its pre-call state:
comm.status → "waiting"(ball is with employer while the written counter is drafted/sent)comm.nextActionDue → <counter due date, e.g. next business day ISO date>comm.nextAction → "Await employer counter"comm.draft → null(no outbound draft pending — email-comms owns it) This clears the pre-call CTA and replaces it with the correct waiting state so the per-job action cell and Next Steps card reflect the live negotiation state.
- Append the call summary to
conversations[]inworkspace/tracker.json(STEP 6 mechanics). - Confirm-first before any comp-boundary write-back that lowers a floor or target; a raise applies directly and is logged with an Undo:
- Per-offer walk-away floor ("I won't go below $X on this one"): use
careerrat gate comp-floor <N> --write --confirmand echo the CLI confirmation. - Permanent sourcing floor change:
careerrat gate comp-floor <N>(dry-run);careerrat gate comp-floor <N> --write --confirmto commit. - Comp target change:
careerrat gate comp-target <N>(dry-run);careerrat gate comp-target <N> --write --confirmto commit.
- Per-offer walk-away floor ("I won't go below $X on this one"): use
STEP 4 — Lint the packet
Run:
careerrat lint-placeholders workspace/interview-prep/<company>-<role>.mdFix every [Company], [Role], <insert>, or unfilled placeholder before
surfacing the packet. Do not mark the packet build-ready until lint passes
clean.
STEP 4b — Export packet (PDF only)
After lint passes clean (STEP 4), render the styled PDF — the only export the packet ships:
careerrat export workspace/interview-prep/<company>-<role>.md --pdfUses the Playwright Chromium bundled with the repo — no setup required. The PDF
carries the full editorial styling (navy masthead, the Fit Snapshot table,
callouts). PDF only — do not also emit --docx. The DOCX path runs through
pandoc, which strips the styling into a plain text copy that undersells the
packet; the PDF is the artifact to keep and share. If a prior run left a
<company>-<role>.docx next to the packet, delete it.
STEP 5 — Post-interview debrief capture
This step runs after the user returns from the interview and shares notes, a transcript, or a debrief.
Write a debrief file to:
workspace/interview-prep/<company>-<role>-<yyyy-mm-dd>.mdStructure the debrief file with these sections verbatim:
## Status
Outcome: <advancing / rejected / pending / offer / withdrawn>
Next step: <what and by when>
Process owner: <recruiter name, HM name>
Blockers: <open items>
## What They Tested
<skills, scenarios, case questions, or assessments the interviewer focused on>
## Stories That Landed
<which evidence claims or narratives the interviewer responded well to>
## Objections and Risks
<gaps flagged, concerns raised, pushback heard>
## Sharper Answers For Next Time
<what to say differently, with the revised framing>
## Comp-Level Strategy
<what comp signal emerged; what they said or didn't say; adjust anchor if warranted>
## Packet Updates Needed
<evidence gaps to fill, positioning to sharpen before next round>Outcome write-back — same write as conversations[] append. After writing the debrief file and appending conversations[], also update these fields in the same tracker.json edit — partial writes leave ghost CTAs:
app.status→ transition to the outcome stage:"advancing"(next round confirmed),"rejected","offer", or"withdrawn"as matched by the debrief Status line. Do not leave it at the pre-debrief stage.app.stage→ next-round label (e.g."Onsite","Technical") or terminal label (e.g."Rejected","Offer received").jobs[].nextAction→ rewritten to the next concrete action (e.g."Prepare for panel — <date>") when advancing; set tonullwhen terminal (rejected / withdrawn).jobs[].nextActionDue→ next-round date when advancing;nullwhen terminal or when the ball is with the employer (pending/offer-review).- Comm thread (
comm.status,comm.nextActionDue,comm.nextAction): update alongside the app fields —comm.status → "waiting"when pending/advancing and it is the employer's turn;comm.status → "closed"when terminal. jobs[].followUp: when the outcome isadvancing,pending, oroffer, write a follow-up so the Focus card auto-advances past the now-passed interview slot:{ "dueAt": "<interview date + 1 business day, ISO date>", "kind": "post-round-follow-up" }Compute the due date as the next business day (Mon–Fri) after the interview date in
app.interviewAt(ornextInterviewAt). Use"post-round-follow-up"as thekind— never use the word "interview" in this field. When the outcome is terminal (rejected/withdrawn), setfollowUp → nullto clear any prior entry.
Bank what landed. For each story under "Stories That Landed", record it back to
the bank so the next loop opens with it ranked. Re-add the story (an upsert by id)
with this round appended to its landed list:
careerrat stories add --file <updated.yml> --write. If a sharper framing emerged,
update the story's narrative too — still tracing to the same evidence_ids. A story
that didn't land or got refuted is a note for the candidate, not a silent edit.
When the debrief surfaces a detail that would strengthen a story but you don't have
yet (a number to confirm, a concrete before/after, a claim that conflicts with the
committed code), add it to that story's open_questions[] per STEP 2b step 4 — the
re-add --write mirrors it into tracker.storyEnrichment and the dashboard raises a
"give me more context" card. Don't gate the debrief on it; bank now, flag the gap.
Recording: If the user consented to recording, set recording to the source
(e.g. "Granola (consent given)"). Never auto-record. Remind the user that many
jurisdictions require all-party consent before recording (U.S. two-party-consent
states include CA, FL, IL, WA, and others) — they must obtain explicit consent
from all parties before recording starts. If no recording, leave recording: "".
STEP 6 — Append to tracker conversations[]
Mode detection: run careerrat data status. Exit 0 → DB workspace — use the
careerrat data <verb> commands below (Data Write Contract, AGENTS.md). Nonzero
exit → legacy workspace (no DB yet) — follow the direct workspace/tracker.json
instructions in this step unchanged.
DB workspace: never hand-edit workspace/tracker.json or
workspace/activity.jsonl; they are generated exports. All content rules in
this step (conversation shape, Content Register routing, dossier gating, the
no-overwrite guard, atomic stage write-back) apply identically — only the write
mechanism changes:
Append or replace
conversations[], row-level typed fields, andartifacts.interviewDossierby reading the current exported application row, building the full patched field set, and writing it with:careerrat data app set-fields <application-id> --data '<patch JSON>'When setting a confirmed future interview datetime and the existing value is absent or in the past, use the booking-aware verb first:
careerrat data app schedule-interview <application-id> --at <ISO datetime> --round "<canonical round kind>" --note "<interviewNote>"Then use
app set-fieldsfor the conversation entry, dossier artifact,stage,nextAction,nextActionDue,compNote,roleFit, or other typed fields. If a futureinterviewAt/nextInterviewAtalready exists and the invite context shows a different time, do not overwrite it here; flag the discrepancy and hand the reschedule toschedule-meeting.When the prep or debrief changes pipeline status, use:
careerrat data app set-status <application-id> <status> --note "<statusNote>"Then use
app set-fieldsfor non-status typed fields that are not part of the status verb.When STEP 3b offer-call handling also resolves a communication thread, patch the full communication row and persist it with:
careerrat data comm upsert --data '<patched full communication row JSON>'After DB writes, run:
careerrat data verify careerrat tracker --verifyRun
careerrat trackerafterward only when a recovery snapshot is useful. Add a richer packet/debrief Activity Pulse event only if needed withcareerrat data activity append --data '<activity JSON>'; the domain verbs already export and log their own audit events.
Locate the application row in workspace/tracker.json: match
applications[].id first; if no id, match by applications[].company +
applications[].role. If no row exists, halt and report — do not create a
stub row here; use apply-job to initialize the record first.
Append an entry to conversations[] on the matched row. Write typed,
single-topic fields per the Tracker Content Register in AGENTS.md — don't
concatenate comp, process, and coaching into notes:
Round
kindvalues follow the canonical Round Vocabulary in AGENTS.md — never numbered; a virtual onsite isonsite, notfinal;finalonly when the process is confirmed to end there.
{
"date": "<ISO date>",
"kind": "recruiter screen | assessment | technical | hiring manager | onsite | final | offer",
"who": "<name — title>",
"notes": "<2 sentences max: what happened + immediate next step — ≤200 chars>",
"compNote": "<1 sentence comp/band signal heard, ≤140 chars — else omit>",
"processNote": "<1 sentence: next round + who + timing, ≤160 chars — else omit>",
"learnings": [
{ "label": "<≤50 chars>", "note": "<≤100 chars>" }
],
"recording": "<source or \"\">"
}Then route the debrief sections to the right place:
- "Status / outcome + next step" →
conversations[].notes(2 sentences). - "Comp-Level Strategy" →
conversations[].compNoteand the row-levelapp.compNote. - "What's next / process" →
conversations[].processNote. - "Objections / Sharper Answers / Coaching" →
conversations[].learnings[](labeled items, ≤5). - Durable cross-role patterns →
candidate/learnings/<family>.mdonly (STEP 7).
Also set the row-level app.interviewNote (≤60 chars, format
<Round> — <Weekday> <Date> <Time> TZ with <First Name>, e.g.
Onsite loop — Tue 03-10 14:00 PT with Alex Rivera) when this entry records a
scheduled/upcoming interview — that single line is what the Focus card shows.
Persist the dossier for the Focus-card preview. The dashboard promotes a row as the Focus interview only when app.interviewAt or app.nextInterviewAt is a structured ISO datetime that is in the future. Do NOT write artifacts.interviewDossier unless one of those fields is set and its datetime is in the future — writing a dossier onto a rejected, stale, or unscheduled row creates an orphaned artifact that can never appear on the Focus card. If neither field is set, do not generate a dossier: ask the user for the interview datetime, or hand off to schedule-meeting to book the slot and write the canonical datetime first, then return here.
When a future interviewAt / nextInterviewAt is confirmed, the dashboard auto-promotes the row as the featured/adaptive Focus item and its "Open dossier" button opens the prep packet full-page (NOT the generic job drawer). So whenever you build a packet (STEP 2) for a confirmed upcoming interview, also write the rendered packet onto the row so the preview has something to show:
"artifacts": {
"interviewDossier": {
"title": "<Company — Role>",
"round": "<round/audience, e.g. hiring manager>",
"path": "workspace/interview-prep/<slug>.md",
"generatedAt": "<ISO date>",
"markdown": "<the full packet body written to the .md file>"
}
}markdown is the exact body you wrote to workspace/interview-prep/<slug>.md — it
stays candidate-private (tracker.json is gitignored), is rendered read-only, and is
never sent outbound. Refresh it on each re-prep so the preview never goes stale. If
no packet exists yet, leave the artifact unset — the modal shows a "prep this
interview" prompt rather than a broken preview.
Stage write-back when a confirmed interview date is known. When the invite has a confirmed date, write all of the following in the same tracker.json edit as the interviewDossier artifact — do not defer any field to a later write:
app.status→ the matching pipeline stage (e.g."interviewing")app.stage→ the round label (e.g."Recruiter screen","Hiring manager","Onsite")app.interviewAt→ full ISO datetime string including timezone (e.g."2026-07-10T14:00:00-07:00"). UsenextInterviewAtwhen there are multiple confirmed future rounds and a current round is already set. Never writenextInterviewDate— the dashboard does not read it. No-overwrite guard:schedule-meetingis the booking authority for these fields. Only writeinterviewAt/nextInterviewAtwhen the current value is absent or is a past datetime — if a future datetime is already set and the invite context shows a different time, do not silently overwrite it; flag the discrepancy to the user and hand the reschedule toschedule-meeting.jobs[].nextAction→"Attend interview — <date>"(e.g."Attend interview — Thu Jul 10")jobs[].nextActionDue→ the same ISO date
This makes the Focus card and Next Steps CTA reflect the actual next event. A partial write (dossier set, stage fields not updated) leaves a ghost CTA — write everything atomically.
Legacy workspace (no DB): after editing tracker.json, run in sequence:
careerrat tracker --verify
npm run verify:tracker
careerrat trackerBoth verify commands must pass clean before snapshotting.
Then log the packet or debrief to the Activity Pulse feed (see Activity Pulse in AGENTS.md):
careerrat activity append --type interview --actor agent \
--title "Interview prep — <Company>" --summary "<packet built / debrief captured>" \
--company "<Company>" --app-id <application id> --writeSTEP 7 — Write durable lessons to learnings file
Compose the entry body as markdown covering the durable patterns from this interview:
- Positioning that landed (include the framing that worked)
- Objections heard with sharper answers for next time
- Comp signals (what the employer revealed about budget/flexibility)
- Recurring questions for this role type
- Keywords and titles that resonate
Capture only durable patterns. Do not record one-off conversational detail. Keep it honest — never invent a lesson that didn't happen.
Write the composed body to a temp file, then dry-run (lint) and commit via the
learnings CLI (the helper creates candidate/learnings/ and the family file on
first write):
# dry run — lints for placeholders and comp leaks, prints what would be appended
careerrat learnings append "<role>" --title "<short label>" --body-file <path>
# commit
careerrat learnings append "<role>" --title "<short label>" --body-file <path> --writeNever include current_base or "currently make" language in the entry body —
the CLI will refuse it.
Rules
- Separate sourced facts from JD-inferred prep. Label likely questions and inferred signals explicitly; never present inference as evidence.
- Use
candidate/evidence.ymlverbatim. Never invent stories, metrics, or claims. Surface gaps so the user can fill them before the interview. - Select across the WHOLE evidence bank, ranked by relevance. When building Top
Fit Signals and the JD Fit Map, consider every claim in
candidate/evidence.ymland pick those with the strongest, most role-specific overlap to the JD — not the first familiar matches. Recently added claims (a new repo/project appended to the bank) are easy to overlook, and a high-overlap claim must not be shadowed by an earlier one that merely shares a single signal. TherenderInterviewPacket()scaffold now selects this way (diversity- and overlap-aware), but apply the same discipline when authoring sections by hand. - Stories trace to evidence and compound. The STAR+R bank
(
candidate/stories.yml) is drafted fromevidence.yml, never invented; reuse it across loops viacareerrat storiesand bank what lands. A behavioural gap with no backing evidence is an Evidence Gap, not a story. - Never include
current_basein any packet section, debrief, or tracker note. Anchor outbound comp ontarget_base(oroe_max_basefor OE roles);minimum_base/oe_min_baseare internal walk-away references, never surfaced outbound unless the user explicitly instructs.expected_basegoes outbound only when a comp form requires it. - Segment prep by audience. Questions to ask, likely questions, and the comp script differ by round type (recruiter ≠ HM ≠ panel ≠ offer). Expand sections as more audience context is known.
- Write gates back when the user states them. Any new gate (comp floor, exclusion, honesty boundary) stated mid-session must be written to the canonical config file — never left only in chat.
- Domain-general. The skill reads candidate config and makes no assumptions
about industry, tech stack, or role type. If
job.signalsis sparse, derive from JD body andtargeting.yml#role_bucketsrather than leaving sections empty. - Don't bury the answers that win the round. The few highest-leverage answers
(impact metrics, the "can you do X"/weakness reframe) belong on the lead-off
Say-This / Never-Say-This Card in memorizable form — a correct answer buried at
packet depth is, in the room, the same as not having it. Surface verbatim numbers
from
evidence.yml; deliver every stated limit with its constructive reframe. - Every likely question gets a scripted answer or a surfaced gap — never a bare list. A question with no banked story is not skipped and not improvised: it's flagged 🔴, the candidate is asked for the anecdote with two evidence-grounded example angles to jog memory, and the resulting story is drafted and banked before it becomes a packet answer. Shipping a packet that just lists the questions is the anti-pattern this skill exists to prevent.
- Prep compounds: anchor each round to what prior rounds revealed. A
company's stated priorities and "big bets" (captured in
conversations[].processNoteandlearnings[]from earlier rounds, per STEP 0 item 1) are the most company-specific signal there is, sharper than the JD. Read every prior round's entry before building, and anchor the packet's examples and answers to those priorities alongside the generic prep. Each interview's debrief (STEP 5/6) is meant to feed the next round's packet, that compounding loop is the point of capturing debriefs at all. - Design rounds need diagrams, the why, and the probe (STEP 2c). For any technical or system-design round, every worked example carries an ASCII diagram the candidate can reproduce, the reasoning for each component choice attached to that choice (not a separate list), and the likely follow-up probe with its scripted answer. Include at least one example grounded in the company's stated priorities and one or two generic ones. A box with no defensible reason, or a talking-point list with no diagram, is not done.
- Plain language, no buzzword chains. Write every section so a smart non-specialist hiring manager follows it. Lead with what the thing does and why it matters, then name the tech once if it earns trust — at most one parenthetical of jargon. Never stack tools or acronyms back to back (not "hybrid RAG via Voyage + pgvector + RRF + Cohere rerank" — instead "Demo Docs Assistant searches the docs by keyword and by meaning at once, then re-ranks, so it finds the right answer even when the wording differs"). The candidate has to be able to say these sentences out loud in the room; buzzword strings don't survive a follow-up question. This applies to every section, the JD Fit Map most of all.
Code dependencies (shipped)
renderInterviewPacket()audience param. The packet renderer atsrc/core/interview/packet.mjsacceptsaudience(recruiter|hiring-manager|technical|panel) and emits an audience-focus section viaaudienceFocusNote(). Pass the round type so the packet's depth matches the audience.classifyRoleFamily()domain-general families.classifyRoleFamilyinoutcome-analysis.mjsaccepts atargetingarg and resolves families fromtargeting.role_families→role_buckets→ built-in tech fallback, so non-tech roles no longer collapse toother. STEP 0'srole_buckets[].namederivation matches what the code does.- STAR+R story bank.
renderInterviewPacket()accepts an optionalstoriesarray (fromloadStories()insrc/core/interview/story-bank.mjs) and renders a "Prepared Stories (STAR+R)" section by matching stories to JD signals — rendering only provided stories, never inventing one. The bank is validated bycareerrat stories check; every story traces toevidence.ymlclaim ids. buildLikelyQuestionsSection()scripted-answer scaffold + gap flagging. Insrc/core/interview/packet.mjs, this renders each likely question with a deterministic anchor: a banked story (matched viafindStoryForQuestion()against the story'sprompts[]), the Positioning Thesis (opener), the JD Signal Match row ("experience with<signal>"), or a 🔴 behavioural-gap flag when nothing backs it. Returns{ markdown, gaps };renderInterviewPacket()appends a "Behavioural Gaps" section listing the unbacked questions. The renderer never authors the answer — it marks the anchor and instructs the agent to script the verbatim words (STEP 5) and to seed any gap via STEP 2b. Add a question to a story'sprompts[]to make it auto-anchor next loop.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

