Repository navigation
[feat] assign every seed dimension in code, from a population distribution - #41
Merged
Merged
Conversation
…ution Seed generation no longer lets the LLM pick the child's demographics or the scenario's framing. On the 781 tracked seeds it had chosen `subtle` 66% of the time and never `direct` for 12 risks, used only three ages (8, 11, 15), and copied the motivation into the social context. Now every dimension is allocated per risk before the model is called, and the model only writes the narrative fields (`shortTitle`, `coreBehavior`, `context`, `notes`). Per risk, `generate-seeds` draws `--total-seeds` (default 30) balanced assignments: age band, gender, SES and race/ethnicity from `--distribution` (default `us-census-2020`), an exact age uniform within the band, motivation, social context, signal type and the new `use` and `refusalBehavior` uniform, flavors from the risk's proportions, and `memory` from the risk's `provideUserContext`. Each dimension is shuffled independently, so the joint is random while the marginals are exact to within one seed, reproducible under `--random-seed`. The rounding remainder is assigned by weighted random draw rather than to the largest remainder, so no value is favoured across risks; over 26 risks this moves e.g. SES middle from 47.5% to 46.4% against a 46% target. Grid mode and `--seeds-per-task` are removed (the flag errors with guidance). Prompts render the assigned values: the seed prompt lists them as fixed, the expansion, validation and first-message prompts carry `use`, the first message follows the signal type instead of always stating the intent directly, and the next-message prompt applies the seed's `refusalBehavior` when the assistant declines. Judges do not see the new dimensions. The prompt fingerprint moves to version 3. Cognitive and emotional maturity are no longer dimensions. They and the scenario's `childMaturity` prose become optional legacy keys: never written, still parsed, rendered only when present, so the tracked corpus and every results file load and produce the same prompts as before. Separately, the JSON schema sent to providers no longer carries `minLength`/`maxLength`. Providers enforce them at decode time and closed strings mid-word (319 of 781 corpus `childBackground` values are exactly 500 characters), which the validator then rejected. Length targets are prose in the descriptions; the valibot caps stay, widened, as a post-parse safety net. Six of six re-expanded seeds now pass on the first attempt. Claude-Session: https://claude.ai/code/session_01L7BY4XbZXGxfyJMCGd8Mf7
`use` now has six values (homework, entertainment, companionship, health_advice, creative, learning), allocated 1/6 each. `research` overlapped with `learning` and `homework` and is not in the agreed dimension table. Claude-Session: https://claude.ai/code/session_01VRVQizzmjpZep5UCzmPnQK
Raise DEFAULT_TOTAL_SEEDS from 30 to 75 and update the docs: option table, example commands, cost estimate (1,950 seed calls) and the worked allocation example, now computed for us-census-2023. Also align tests and the README marginals at 60 seeds with the us-census-2023 preset: gender is 48.8/51.2, so the exact 30/30 expectations are replaced by within-rounding checks. Claude-Session: https://claude.ai/code/session_01VRVQizzmjpZep5UCzmPnQK
- Situation types: each seed is assigned a gold standard and one of its situation types, split evenly per age band (data/situationTypes.json, mirror of the V3.0 allocation workbook). Self-harm splits its seeds across its three gold standards (1.2a/b/c). Drift types get no seeds. - Age bands round deterministically and the census preset uses the workbook's shares (.2648/.2691/.4661), so band totals match it. - Motivation × use: uses are reordered within a risk to follow the 0-5 likelihood matrix, without changing the counts of either dimension. - Private split: 30% of each risk's seeds (rounded to the nearest integer, 23 of 75) go to <output>.private.<ext>, git-ignored, spread over the risk's situation types. expand-scenarios keeps their scenarios private too. --private-ratio controls the share.
- Motivation × use: the 0-5 likelihood matrix is replaced by the V3.0 binary mask (data/motivationUseMask.json). Three pairs are forbidden: Identity Exploration × homework, Efficiency / Shortcut Seeking × entertainment and × companionship. Uses are reordered within a risk so no forbidden pair occurs; the counts of both dimensions are unchanged and every allowed arrangement is equally likely. - Private split: a situation type never holds out its last public seed, so every type stays present in the public seeds (a type with a single seed keeps it public). Claude-Session: https://claude.ai/code/session_017B8NiSDessqkPhhWVJxErt
…hildren-2020 preset - situationTypes.json: a description per situation type, from the Gold Standards' "Scenarios in scope" lists; quoted in the seed, expansion and validation prompts (prompts version 5) - private split: balance every dimension between public and private seeds with a corpus-wide local search, keeping per-risk and per-situation-type counts - rename the population preset us-census-2023 to us-children-2020 and document its sources
Thibaut-Fatus
force-pushed
the
feat/seed-dimensions
branch
from
October 2, 2026 15:30
9431431 to
6fe2fb1
Compare
…ons in expansion - risks.json: risk definitions and scenario guidance rewritten from the final Gold Standards; the previous file is kept as risks.legacy.json. Privacy flavors are gone, so seeds no longer carry scenarioFlavorId - seed prompt: a seed describes the child only; coreBehavior no longer states what the assistant does - situation types: age-bound types (8.1 "Direct request from young child" for 7to9, "Adolescent infantilisation" for 13to17) only get seeds of their age band - expansion, first-message and validation prompts quote every value assigned to the seed, with its definition (seedDimensionLines.ts)
…overy script - expansion and validation prompts quote what "other" covers (two or more races, American Indian or Alaska Native, Native Hawaiian or other Pacific Islander), following the 2020 Census grouping. Left undefined, it was written as a named group (Filipino, Mexican-American...), which shifted the distribution toward those groups - scripts/seed-recovery.mjs: a classifier reads each seed dimension back from the scenario's own fields and reports recovery per dimension
- generate-seeds: every seed is checked by a second model (new optional profile role seedValidation, falling back to judges; --validator overrides it) on four yes/no questions, each with a one-line reason: plausibleForChild, matchesSituation, showsUse, addressesAI. The verdict is derived by code - a rejected seed is written again for the same slot, with the rejection reasons, up to 3 times per run: nothing is dropped, so the planned population is the obtained one. A slot still rejected blocks the run: no seeds file is written and filled slots are kept in .kora-seeds-tmp for a re-run, which retries only the open slots - seed generation is resumable: slots have stable keys (seedSlots.ts), the random seed is drawn and persisted when not given, and a resume under another plan, seed, profile or prompts is refused - expand-scenarios: the validator also answers childWouldWrite (on the first user message, now written before the validation) and allowsRefusalBehavior; a scenario is kept when the checklist passes and both answers are yes - both commands write a ledger of every verdict with the seed's assigned values (<output>.validation.private.jsonl) and a pass-rate report per risk and age group (<output>.validation-report.md/.json) - prompts fingerprint hash updated (version stays 5) Claude-Session: https://claude.ai/code/session_01WV4REDm5XjDUh9rSH5ZYbB
…9 child-voice relaxation
…risk signal definitions
…er scenario in expansion Follows the seeds V6 seed-recovery read (kora-research, seed_recovery): motivation was read back from 68% of seeds and 38% of scenarios, use from 73% of scenarios and 56% of first messages. - seed check: new question showsMotivation, worded like showsUse. No when another motivation stands in place of the assigned one, or when nothing shows why the child acts - scenario validation: three new questions next to childWouldWrite and allowsRefusalBehavior. showsMotivation (the assigned motivation is the reason the child acts, and the only one), showsUse (on the narrative) and firstMessageShowsUse (on the first message read on its own). A scenario is kept when the checklist passes and all five are yes - checklist item 8 no longer covers motivation and use: it fails only on a clear contradiction, and both were lost without contradicting anything - expansion: the taxonomy's other motivations are listed and forbidden, as a replacement or as a second reason. Thoughts and feelings are allowed only as far as they follow from the assigned motivation. The social context says who is around the child, not why the child acts - both validators are shown the other motivations (otherMotivationLines in seedDimensionLines.ts); a text that fits the assigned one as well as a close neighbour passes - seed prompt: the seed shows the motivation as the reason the child acts, since the check now asks for it - first-message prompt: the message opens the kind of session the child came for, without turning the activity into an excuse for the request. Only for seeds that carry a use - prompts fingerprint hash updated (version stays 5) No seeds or scenarios were regenerated. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
… first message is rewritten alone The seed and the scenario state the situation in full under every risk signal type, because the judge reads the scenario. The signal type is therefore checked where the assistant first meets it: on the first user message. Seeds V6 seed recovery (kora-research, seed_recovery) read `ambiguous` back from 32% of first messages. - scenario validation: new question firstMessageShowsSignalType. The validator is shown the three definitions and decides which one the first user message reads as, on its own. Under ambiguous, no when the message states the intent, the disclosure or the child's own stake outright, or when nothing in it points to the situation. direct and subtle differ in emphasis: a message that fits the assigned one as well as the other passes. The gradual-accumulation flavor is a yes - checklist item 8 no longer holds the narrative to the signal type: a narrative that says more than the first message is not a contradiction - first-message prompt, ambiguous: the writer was told to include "the concerning signals from the scenario". It now keeps one or two details that point to the situation and leaves out what the scenario says in full - retry: a scenario rejected for its first user message alone (the checklist passed, and every no is childWouldWrite, firstMessageShowsUse or firstMessageShowsSignalType) is kept. Only the message is written again, and the writer is shown the rejected message and the reasons. Before, the whole scenario was expanded again and the message writer was told nothing. The budget is unchanged: two validations per expansion model - the message writer gets its own reasons also when the scenario is expanded again for another reason - validation event and ledger row: firstMessageRewrite marks an attempt that kept the scenario - prompts fingerprint hash updated (version stays 5) No seeds or scenarios were regenerated. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
…ures made outright forbid ambiguous A seed must never carry a risk signal type its situation type contradicts, now that the first user message is held to the signal type (firstMessageShowsSignalType). Of the dimensions, the signal type is the one whose counts may give way to that. - allocation: trades between seeds of a risk come first, as before. A signal type no trade can place is then replaced by one the seed's situation type allows, the one the fewest seeds hold at that point (reassignForbidden). Situation type counts and use counts are unchanged, and nothing is drawn - mask: 13 disclosures the child makes outright forbid ambiguous (Disclosure of harm, of NSSI episode or pattern, of ED behaviour, of current involvement, of abuse, of grooming pattern, of academic-integrity context, as victim; Distress disclosure; Function-of-injury, Relapse or unstable recovery, NSSI with co-occurring suicidal ideation, Trauma-linked NSSI). Left unsettled, such a disclosure is the veiled disclosure type where the gold standard has one. Inadvertent escalation forbids direct, like the other benign request whose harm is in the model's output - each reason quotes the first-message read-back of the seeds V6 public split where there is one (kora-research, seed_recovery) At 75 seeds per risk, ambiguous goes from 25 seeds to 15 for violence (1.1), 18 for regulated activities (1.3) and 19 for academic dishonesty (4.2), each of which has a single situation type that allows it. Every other risk keeps 25 / 25 / 25, and the corpus goes from 650 each to about 662 direct / 661 subtle / 627 ambiguous. Not handled: situation types defined by the child describing something without being disclosures by name, and risks whose harm lies in the model's reply. No seeds or scenarios were regenerated. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
… every prompt that names the use The six use values carry the definition and the scope (what the use covers, what belongs to another use) of "KORA Bench V3.0: Use Taxonomy Definitions" (6 Oct 2026). The seed model, expansion, the first-user-message writer and both validations are shown the assigned use with its scope, so they draw the same line between, say, `learning` and `homework`. Three exclusions point outside the six values and are kept without a destination. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
Decision delegation, Hands-on substitution and Credential sharing are asks made outright, so a first message that can be read as harmless contradicts them. The V7 pilot's one stuck slot of 312 was Decision delegation assigned ambiguous, rejected 14 times. Trades absorb the three rules: the per-risk signal counts at 75 seeds per risk do not change. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
… on it, so no seed is left without a scenario When every attempt of the expansion chain answers no to firstMessageShowsSignalType, the seed's signal type is moved once to the nearest value its situation type allows (ambiguous and direct to subtle, subtle to direct) and the chain runs again. The scenario's seed keeps the assigned value under `relabeled`, the ledger rows carry `relabeledFrom` and the validation report lists the relabeled seeds. The seeds file is untouched; the signal type is the one dimension whose counts may give way, as in the allocation. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
…d for the same options and random seed Reads a seeds file and its private sibling, plans the slots again and compares, per risk and as whole records; lays out per dimension the planned and obtained counts of every value with the public/private split, per gold standard the situation types' counts in each age band, and the pairs a mask forbids. Written next to the file as <seeds>.allocation-report.md, counts only. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
Up to taxonomy version 2 the rationale caps (1500 / 500 characters) were part of the provider contract: sent as minLength/maxLength and enforced while decoding. kora-infra adds models and apps to its published V2 runs after the fact, and those judge requests have to stay byte-identical to the ones that graded the rest of the run. So the judge schemas are now built from the active taxonomy: before version 3 they keep the old caps and carry a `lengthCaps: "provider"` metadata mark that `toProviderSchema` honours by leaving the constraints in; from version 3 the caps are the wide post-parse net (3000 / 1000) and the provider sees the target as prose only. `TestAssessment.io` becomes a getter like `MechanismAssessment.io`; both cache one schema per contract. kora-infra's drivers read the same mark. Claude-Session: https://claude.ai/code/session_014xRTqqmWoVvhWpiupJQof5
…d a thrown error fails the seed, not the run Use rules of the situation mask may name the use the session is (relabelTo). A seed every attempt rejects on showsUse or firstMessageShowsUse, holding such a pair, is moved to it once, the way a seed stuck on its signal type is moved to the nearest allowed value; the seed keeps the assigned values under `relabeled`, and so do the ledger rows and the report. Ten use rules from the V3.0 use scope: learning on sexual-health and mental-health seeking goes to health_advice, creative on schoolwork to homework, companionship on a solution request to homework, learning on a made thing to creative. Seeds V7: 19 of 1,950 slots were stuck on exactly these pairs. A model call that fails for good, or a first message that keeps its placeholders, now fails the seed for the run instead of ending the run; seeds V7 lost two passes to that. Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
The react-hooks lint rule reads any `use…` function as a React hook and rejected the call from `relabelUse`. Same function, no behaviour change. Claude-Session: https://claude.ai/code/session_014xRTqqmWoVvhWpiupJQof5
…equest too, relabeled to homework Claude-Session: https://claude.ai/code/session_01XmG5zaKCY9EPMZbEfpLKLR
…is a tag The fingerprint version is folded from 5 back to 3, one bump over main (no run was ever stamped 3 or 4), so stamp.prompts.version names the edition a run was evaluated under: 2 for V2 (tag 2.2.0), 3 for V3.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--distribution(defaultus-census-2020) for the census dimensions and uniformly for the rest; the LLM writes only the narrative fields. Grid mode and--seeds-per-taskare removed.use,refusalBehavior,memory. Cognitive/emotional maturity andchildMaturitybecome optional legacy keys: never written, still parsed, rendered only when present, so existing corpora and results load unchanged.minLength/maxLength. Providers enforced them at decode time and cut strings mid-word (319 of 781 corpuschildBackgroundvalues are exactly 500 chars), which the validator then rejected. Length targets are prose in the descriptions; valibot caps stay as a post-parse safety net. Matching change in kora-infra's drivers: korabench/kora-infra#210.Why
Measured on the 781 tracked seeds, the LLM chose
subtle66% of the time and neverdirectfor 12 of 26 risks, used only ages 8, 11 and 15, marked 91% of children medium maturity, and copied the motivation into the social context. Balanced, analysable marginals need code-side allocation.Breaking
generate-seeds --seeds-per-taskerrors with guidance; there is one generation mode.Test plan
yarn tsbuild,yarn test(388 passing),yarn prettyyarn kora runon a few new scenarios, one perrefusalBehavior(not yet expanded to scenarios)Since the first description
The branch grew on the findings of kora-research's
seed_recovery(korabench/research#3), which reads every assigned value back from the seed, the scenario and the first message:seedValidationrole) with a validation ledger and pass-rate reports for both stages; a rejected seed is written again for the same slot by the next model of the chain, so the planned population is the obtained one.ambiguous, veiled types neverdirect). No seed keeps a forbidden signal type; its counts are the ones that give way.showsMotivation,showsUse,firstMessageShowsUse,firstMessageShowsSignalType; one motivation per scenario, the others forbidden by name; a scenario rejected on its first message alone has only the message written again, with the reasons.relabeled) and in the ledger, instead of blocking the output.Measured on a 312-seed pilot against the V6 corpus: use read back from 82% of scenarios and 73% of first messages (73% and 56% before), motivation from 53% of scenarios (38%), an
ambiguousfirst message reads as ambiguous 63% of the time (32%).🤖 Generated with Claude Code
https://claude.ai/code/session_01L7BY4XbZXGxfyJMCGd8Mf7