Rename to affect-kernel, ground every constant, and evaluate the scorer - #6
Merged
Conversation
Name the library by what it is rather than by the application it was extracted from. "anjo-core" reads as the core of one product; the code is a general affect kernel and the game-NPC example already demonstrates that. - Python distribution and module: anjo-core/anjo_core -> affect-kernel/affect_kernel - npm package: @anjo-ai/core -> affect-kernel - prose and repository URLs updated throughout No version was ever tagged or published under the old name, so nothing installed is affected. Provenance references to Anjo are deliberately kept. Behavior is unchanged: identical test counts and coverage before and after (Python 173 tests / 90%, TypeScript 160 tests / 95.84%).
The library named OCC, PAD, ALMA, and Big Five and cited nobody. A reader had no way to tell which numbers came from published work, which were tuned against the Anjo deployment, and which were arbitrary. docs/foundations.md now tags every constant L (literature), P (production-tuned), or B (bounded choice), and states the departures rather than glossing them: - appraisal is a lookup table on a pre-classified intent, not an appraisal process over OCC appraisal variables the way EMA and FAtiMA are; - mood decay is per turn, not per unit of wall-clock time; - retrieval multiplies where the closest published comparable sums, and decays recency linearly where human forgetting follows a power law; - the mood-congruence asymmetry has no citation behind it at all; - decoder controls have no literature behind them at all; - O, C, and A are validated, stored, and then ignored. Also adds a "what would falsify these choices" section, so the claims are answerable rather than decorative. All 14 DOIs and 5 arXiv IDs verified against Crossref and the arXiv API. CITATION.cff validates against CFF schema 1.2.0 (cffconvert). Documents one behavior that was implemented but absent from algorithm.md: the ambiguous-intent valence amplification (x1.10 negative, x1.04 positive above |v| >= 0.20). Verified against the runtime, not read off the source. No behavior change: 173 Python tests / 90%, 160 TypeScript tests / 95.84%.
foundations.md pointed at analysis/ and bench/ directories that do not exist yet. Replaced each with either a measured number or an explicit statement that the work has not been done, so the document makes no promise the repository does not keep. The mood half-life is 2.25 turns at the default personality (phi 0.735), computed from the runtime rather than estimated.
The kernel already let a domain replace every word it emits — stage names, expectation cues, turn-shape rules, presence labels — while every number stayed compiled in. A game, a tutor, or a support agent could rename the rungs but not disagree with the inertia curve without forking. AffectDynamics and RetrievalWeights close that gap in both runtimes: inertia base and trait terms and clamp, the resting-dominance coefficient, the baseline blend, per-emotion carry decay and its fallback and floor, the ambiguity amplification, the recency horizon and floor and parse fallback, significance and rehearsal weights, the episode bonus, and the mood-congruence threshold and its negative/positive asymmetry. Defaults reproduce the pinned contract exactly, which is the proof the refactor preserved behavior: all 225 cross-runtime vectors and 3 longitudinal traces pass untouched in both runtimes. The baseline blend keeps two independent fields rather than a retention plus its complement, because 1 - 0.98 is not exactly 0.02 in binary floating point. 36 Python and 12 TypeScript tests cover the new seam, including that the defaults are indistinguishable from omitting the argument. Three deliberate mutations (dropping the caller's weights at the ranking entry point, pinning carry decay to the module default, ignoring the carry floor) were confirmed to fail exactly the tests that claim to cover them. Python 173 -> 209 tests at 90% coverage; TypeScript 160 -> 196 at 96.06%.
foundations.md labelled most retrieval constants "production-tuned, never ablated" and listed five results that would falsify them. Three needed no model and are now run. bench/ is seeded, dependency-free, and deterministic. Five regimes x 400 queries x 20 candidates, with relevance assigned before any feature is drawn so that ground truth is independent of every formula under test. Regime A exists specifically so the benchmark can embarrass the kernel; regime D is the only one where all its assumptions hold at once. Findings, including the ones against us: - the machinery is not free: -0.175 MRR against plain similarity when its assumptions are violated, +0.415 when they hold; - Park et al.'s additive form beats the multiplicative form by 0.111 MRR in the fairest regime; - but the composition is not the cause. Significance enters this scorer at 0.03 and Park's at 1.0; raising that one parameter lifts MRR 0.858 -> 0.960 against additive's 0.968, with the multiplicative form untouched. The salience term is underpowered, not misshapen; - mood congruence is worth +0.012 MRR in a regime built to favour it; - linear recency is *not* the weak point. This retracts a claim this repository made two commits ago in foundations.md and in the recency_weight docstring: at a matched 30-day half-life linear beats exponential by 0.009 and power-law by 0.044. The claim is retracted in place rather than quietly softened. bench/RESULTS.md is generated, and check.sh and CI fail on drift, so no document can quote a stale number. All 11 figures cited in bench/README.md were verified against the generated table programmatically. Limitations are stated at the same volume as the results: synthetic corpora, machine-assigned ground truth, drawn rather than embedded similarity, one gold per query, no language model anywhere. The README's larger claim — deterministic state beats a prompt-only persona — remains untested and is marked as such.
…fuzzing Two hardening items the roadmap had been carrying. CompanionState -> AffectState and CompanionEngine -> AffectEngine, in both runtimes, with the compound names (createAffectState, AffectStateInput, ResolvedAffectState, AffectEngineOptions) following. A library called affect-kernel whose central type made a game NPC instantiate a "companion" was not coherent. The presence vector's `source` field changes from "companion_state" to "affect_state", updating 8 expected values in shared/golden/kernel_golden.json — a reviewed fixture change under the parity contract, safe because nothing was ever published. Seeded property and fuzz suites in both runtimes, using a fixed-seed PRNG rather than a property-testing dependency so a failure is re-runnable from the seed alone and the zero-dependency claim is untouched. They cover: PAD, baseline, and carry staying in domain across 200-turn adversarial walks; determinism under repeated identical input; appraisal never mutating the caller's state; ranking as a total order that dedupes, sorts, respects its limit, and ignores input order; the length factor never growing a budget; the decoding envelope; Unicode handling over combining marks, zero-width and RTL controls, NEL, BOM, and astral codepoints; and pickle/deepcopy round trips. A mutation that let the length factor grow a budget was confirmed to fail the test that claims to forbid it. Adds docs/threat-model.md, which states the one boundary the kernel actually enforces (untrusted evidence is a separate type, structurally excluded from the system prompt, bounded at 2k/8k characters), the two ways an adapter silently undoes it — flattening the channel on the wire, and returning a no-op transaction — and an explicit list of what the kernel does not defend against, including the affect-derived side channel in response length and temperature. Python 173 -> 227 tests at 91% coverage; TypeScript 59 -> 78 at 96.24%.
A single-maintainer project that does not say so reads as either abandoned or unresponsive when a contribution sits for a week. Says the response time out loud, and orders what gets looked at first — with evidence that contradicts the repository's own claims ranked second, since the linear-recency claim has already been retracted on exactly that basis.
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.
Six commits taking the repository from "well-engineered but unfalsifiable" to
something a researcher can check.
What changed
Rename.
anjo-core→affect-kernelon GitHub, PyPI, and npm; moduleanjo_core→affect_kernel. The library is named for what it is rather thanfor the application it came from. Nothing was ever tagged or published under the
old name. Provenance references to Anjo are deliberately kept.
Provenance.
docs/foundations.mdtags every constant L (literature),P (production-tuned), or B (bounded arbitrary choice), and states the
departures instead of glossing them: appraisal is a lookup table on a
pre-classified intent rather than an appraisal process over OCC variables; mood
decays per turn, not per unit of time; retrieval multiplies where the closest
comparable sums; the mood-congruence asymmetry and every decoder control have no
citation at all; O, C, and A are validated, stored, and ignored. All 14 DOIs
verified against Crossref and 5 arXiv IDs against the arXiv API.
CITATION.cffvalidates against CFF schema 1.2.0.A numeric seam. The kernel let a caller replace every word it emits while
every number stayed compiled in.
AffectDynamicsandRetrievalWeightsclosethat. Defaults reproduce the pinned contract exactly, which is the proof the
refactor preserved behavior — all 225 cross-runtime vectors pass untouched.
An evaluation that reports losses.
bench/is seeded, dependency-free, anddeterministic: five regimes, relevance assigned before any feature is drawn so
ground truth is independent of every formula under test. Regime A exists
specifically so the benchmark can embarrass the kernel.
significance_weight0.03→1.0 lifts MRR 0.858→0.960The linear-recency result retracts a claim this branch made two commits
earlier.
bench/RESULTS.mdis generated and drift-checked in CI, so nodocument can quote a stale number.
Hardening.
CompanionState/CompanionEngine→AffectState/AffectEngine(the last product noun in the public API; updates 8 fixture values under the
parity contract). Seeded property/fuzz suites in both runtimes over clamping,
200-turn adversarial walks, determinism, non-mutation, ranking total order,
Unicode, and serialization — no new dependency.
docs/threat-model.mdstatesthe one boundary the kernel enforces and the two ways an adapter silently undoes
it.
Test plan
./scripts/check.shgreen from a clean checkouttest claiming to cover them
bench/README.mdverified against the generatedtable programmatically
Not done, and marked as such
The README's headline claim — that deterministic state holds a character better
than a prompt-only persona — is still untested. Falsification items (1) and (5)
in
foundations.mdneed affect trajectories with external ground truth thisrepository does not have.