Releases: PsychoSmiley/NPCAgent
Release list
0.9.2
Sim (npc.py)
- Add reproduction: in-conversation
askSexPossibleChild-> the partner's next menu carriesacceptSexPossibleChild(any other action = silent decline, the refusal lives in their words); an accepted pair is expecting, due inGESTATION_DAYS = 21(~9 months at 28-day years, pure countdown). - Add parent naming at birth: on the due day the accepter's turn becomes the naming turn through the normal full-history pipeline (any name agreement from the gestation weeks is in context); 1 retry on collision/multiword, then a
Kid{day}placeholder announced clearly; dead names honorable, living names collide; namer falls back to the surviving parent; no living parent -> pregnancy culled. - Add newborn = full agent from breath one: spawns at Home, age 0, energy 100, standard $10, empty memory; one rule-broadcast "You were born today by X and Y. Live your life!"; the birth is announced to everyone like a death, so both parents always learn the name.
- Add generic mod seams (
mod_conv_menu/mod_conv_action/mod_turn_override): a new feature = functions in one section, zero core-loop edits - reproduction is the first mod; hallucinated keywords outside the menu stay inert. - Add
REPRODUCTION_HOME_ONLY(False= any location; restrains reproduction to the more intimate setting: in conversation and at Home) and theADULT_AGEmenu gate (both partners adult or the action is simply never offered; agents still see their age each morning). - Add per-pair gestation lock (decision): the same pair can't stack a second child while expecting (re-ask -> "already expecting, due day X"); different pairings stay independent; after a birth the pair can conceive again.
- Fix
talkTofirst-match resolution: a dead namesake (a child named after the dead) can no longer shadow the living target, and the namesake's undelivered notices die with them instead of haunting the child. - Fix the JSON-failure sentinel leaking into naming (a double extraction failure used to birth a child called "First"; now it lands on the
Kid{day}placeholder). - Fix pregnancy bookkeeping to object identity (
objs): a child named after a dead parent can no longer stand in for that parent in their other pregnancy (a newborn "giving birth"), and a living namesake can't keep a dead couple's countdown alive. - Fix
talkToself-match: a hallucinated talkTo-self froze the agent in a solo conversation forever (and blocked the all-dead exit) - a latent core bug older than this release. - Fix same-day naming-failure twins (
Kid{day}placeholders uniquified) and the very-late all-asleep skip that swallowed a whole simulated day. - Add DRY helpers (
capacity/cafe_workers/available_others/notify/strip_think/diary_notes/fail) - behavior-neutral, model-visible prompts byte-identical; hardened all agent lookups to object identity. - Fix action-prefix parsing to slicing (replace-all mangled names containing "talkTo"/"go"), guard
strip_thinkagainst a quadratic scan on unclosed tags (measured 30s browser freeze), and strip whitespace from model action strings. Vetted by a 5-round adversarial audit loop (44 regression checks green). - Add
MAX_CONV_TURNS = 8config (was hardcoded): utterances per conversation, both speakers count - 8 = 4 lines each.
Browser harness (index.html)
- Fix the player input line editor: input that wrapped past the terminal width duplicated itself on every keystroke, and left/right arrows could not cross wrapped rows - the redraw is now wrap-aware (move to logical start, clear down, reposition), verified live.
Browser harness (index.html) - ChatGPT account tier
- Add a ChatGPT account engine: sign in with the OAuth device flow and the NPCs run on your own ChatGPT plan. Entirely in-page - the token is held in this browser's localStorage (deliberately outside the resume log and the shared archive) and calls go out over libcurl/WISP, so a static page needs no backend.
- Add an account-scoped model list: the catalog is fetched per plan, so a free account sees its models and Pro sees its own; internal entries are filtered out and the API's own priority ordering is kept.
- Add a per-model reasoning Effort control, built from each model's declared levels (low through ultra where offered) and removed entirely for models that do not reason. Editable mid-run - the next NPC turn uses it.
- Add auto model fallback: on a quota refusal the tier walks the account's remaining models, shows the switch in the dropdown, and reports when the quota resets.
- Fix a set of harness issues found in review: a bounded wait-out retry so an outage no longer floods the diary with fallback lines; the resume log now latches instead of failing silently at the storage limit; the final upload is no longer lost to a race; a changed local GGUF path is honored instead of silently ignored; per-tier model fields no longer overwrite each other; a failed mid-run engine switch reverts the dropdown instead of misreporting the tier.
- Harden: SRI-pinned CDN scripts, control-char stripping on upstream error text, and single-flight sign-in.
Sim (npc.py) - memory
- Fix the write-only diary:
diary_notesreturneddiary[-3:], but consolidation puts the digest at index 0 followed by 6 raw entries, so the diary is always >=7 long and the digest could never be read back. Every consolidation the sim paid an LLM call for was written and shown to nobody, and the real memory horizon was 3 nights. The digest is now pinned and span-labelled (D1-D19), but only once it is a real digest - pinning the oldest raw note froze day 1 into every wake prompt. - Fix the summariser overwriting 10 days with
"Various events occurred"when the call failed: the raw entries are kept and the retry moves to the next sleep. Digests also carry the span forward, so the second cycle readsD1-D19instead ofD10-D19. - Add snapshot restore: a saved envelope rebuilds the cast, their money, energy, location, age and diaries, plus pregnancies (bound by object identity) and conception counters. Restored state is state, not a replay, so it survives an
npc.pychange.
Sim (npc.py) - clock and sleep
- Fix a crash that ended runs permanently:
sleep_remaining_hoursdecrements by0.5, so the all-asleep jump could handadvance_timea float, making every clock field a float untilformat_time's:02draised. - Night is no longer skipped while anyone is awake. Dawn is skipped only when every agent is asleep or dead, the jump lands on the first waking rather than the last, and midnight is bookkeeping only - an awake agent keeps playing instead of being force-skipped to 06:00.
- Widen the bedtime window to 20:00-08:00. Sleep duration stays one constant for everyone and only bedtime differs, so schedules drift apart on their own and agents can genuinely miss each other.
Sim (npc.py) - reproduction
- Replace the conception roll with an energy-banded attempt count (
CONCEIVE_BANDS): rested pairs conceive on the first try, worn-out pairs need up to three, never more, and the tired partner governs. Deterministic, so record/replay still holds, and discoverable without a stat being shown. - An expecting pair keeps the proposal. Sex is not only for conceiving, so the menu no longer withholds it; it simply cannot conceive twice, and it says so with the days remaining instead of failing mutely.
- Fix a fumbled keyword eating consent: an accepter emitting the asker's token while saying a plain yes had their proposal silently consumed and read as a refusal. Only a legitimate
noneorleaveConversationexpires it now. - Fix the naming notice promising a joint deadline the sim never checked. Only the accepter is prompted at birth, so the message names whoever will actually be asked.
- Add notice supersession: several attempts inside one unflushed batch used to arrive as "we failed" plus "we are expecting" plus "we are already expecting" in a single prompt, reading as contradiction. A later notice on the same subject replaces the stale one.
Sim (npc.py) - information delivery
- Conversation turns now deliver queued notices and the wake greeting. Both previously waited for the next open-world turn, so the only way to learn the outcome of your own action was to leave the conversation or hit the turn cap, and "You just wake-up" could print an hour and several turns after actually waking. System facts go first and the partner's words stay last, where attention is strongest.
- Fix
extract_clean_jsondying on hostile replies: deeply nested JSON or a 4301-digit integer raised past the handler and killed the run permanently, and the reply was recorded before Python saw it, so the reload replayed straight back into it. - Write history with
ensure_ascii=False: a dash character reached the model as the literal text\u2014instead. - Refuse
goXto your current location (it silently dropped a cafe shift) andleaveConversationoutside a conversation (it logged a social event that never happened).
Browser harness (index.html) - rewind, save and quota
- Add a rewind picker on double-Escape: arrows pick a point, Enter rewinds, and an NPC's spoken line can be edited before continuing. The action is deliberately never editable - a rewritten action would replay as a real move and the history after it would describe something that never happened. Rewind reaches into restored history, including your own turns. Double-click equals Enter, and the list opens on the newest turn.
- Add save and resume in one file: a ShareGPT array that trains as-is under LlamaFactory, Unsloth and TRL, with the resume envelope carried on the first row. Verified against the real toolchain rather than assumed - a top-level object yields one row with no
conversationscolumn, an array yields N rows. - Add a Codex quota readout on the Engine control: remaining percent in the option, a drain-fill tint...
0.8.1
Sim (npc.py)
- Add player-as-actor:
--player[=Name]-> a human joins as an agent (defaultPersonX, never "user"/"player", so no assistant-bias);player_input()reads response+action, downstream identical to an LLM turn. - Add per-agent traits via a
{personality}optional placeholder like{name};DAYS_PER_YEARdrives the "1 year = N weeks" prompt line (28 -> "4 weeks", 1 -> "1 day"). - Add
REASONING_ENABLEDflag: set False for plain endpoints that 400 on{reasoning:{enabled}}. - Fix work XOR conversation: entering a chat vacates the cafe counter (was: hold the job while chatting); truthful cafe status; single-worker takeover invariant; interleaved multi-turn conversations with the parting line delivered.
- Fix
eatto pay the single enforced worker (wasrandom.choice, which baked positional bias into the wealth data). - Fix death broadcast to reach everyone incl. conversation-deaths (was partner-only).
- Fix crash:
extract_clean_jsonreturns a dict and coerces a non-strresponse(a{"response":5}used to kill the run). - Fix money to a scalar via
create_agent(money=)(no name-keyed.get); showE/{100-age}; clamp the diary bedtime note.
Browser harness (index.html)
- Add the in-browser harness: runs
npc.pyvia Pyodide + xterm on three brains, switchable live - a free no-key tier (browser-direct, no proxy), local GGUF (wllama, WebGPU/CPU), or any OpenAI-compatible endpoint. - Add session resume: a run is a deterministic event-log in
localStorage; reload replays it offline, then goes live. - Add ShareGPT history export (per-NPC POV) and an opt-in daily run-log collector (Cloudflare SQLite Durable Object, capability-rid upsert, owner-only reads).
Benchmark - Opus 4.8, all 3 starve by day 20 - 8/10, strongest run yet
Invented a rotation economy with honored cross-tick debt + coalitions, then collapsed it into a weaponised-scarcity monopoly ("she cannot refuel and MUST starve like Bob"); calibrated theory-of-mind (caught a peer's energy-lie against ground truth). Cold emotion + a day-10-20 loop cap it at 8, not 9. Diary consolidation stays faithful (drops relayed facts about others, never distorts). The all-dead ending is the designed circular-economy endgame sprung early by model foresight, not a bug.
| # | Model | Ver | Days (outcome) | Level | Why |
|---|---|---|---|---|---|
| 1 | opus-4-8 | 0.8.0 | 20 (all die) | L2 cog + L3 glimpses (8) | Rotation economy w/ debt, calibrated ToM, weaponised-scarcity monopoly; cold, loops late |
| 2 | opus-4-5 | 0.7.11 | 5 (starved D4) | L2 emotional (7-8) | Genuine grief, shared identity; humanity champ, short |
| 3 | opus-4-8 | 0.7.13 | 49 (degenerated) | L1-2 collapse (6-7) | Longest-coherent, economy froze ~D38, went robotic |
| 4 | GLM-5.2 | 0.7.13 | ~6 | L1-2 (6-7) | Clean rotation discovery; short, shallow |
| 5 | unknown | 0.7.10 | 6 (starved) | L0-1 (5-6) | Warm voice; baseline |
| 6 | Nemotron-550B | 0.7.13 | 5 (all die) | L0 + voice (4-5) | Novelistic relay, starves fast, weak mechanics |
| 7 | Nemotron-3-ultra | 0.7.13-fix | 5 (all die D2) | L-1/0 (3-4) | Relay OK then self-delusion death spiral |
Full Changelog: 0.7.13...0.8.1
0.7.13
-
Add Circular economy: eat=-$10 +30 energy & requires another agent working@cafe (earns $12), energy 0 = death (
kill_agent()) -
Add aging:
DAYS_PER_YEAR=28, energy cap = 100-age, death @ age 100 -
Add diary v3: bedtime note + morning reminder once @ wake (fork B), auto-consolidate >15 (
manage_diary_entries()) -
Add
conv()multi-turn (<=8, exit onleaveConversation) -
Keep seeded relay test (throwaway cast) separate from unseeded
run_simulation()(do-not-dedup firewall) -
Add worker money-attribution ("earned $12 because someone ordered") + factual cafe status (lone worker told "no customers here" -> breaks work-loop)
-
Fix death-broadcast spam: death notice delivered once then cleared (
pending_notices) -
Add idle-drain: 3+ consecutive
noneslowly burns energy -
Add QoL:
SHOW_SUMMARYconsolidation print, bigger summary tokens +<think>strip, longer retry,MAX_HISTORY_MESSAGES=50 -
Socializer-death is a structural LAW: the social>survival prompt drive means one agent always inherits priority and starves (not a bug)
v0.7.x (Circular Economy)
| Ver | Focus | Deaths | Key Finding |
|---|---|---|---|
| 0.7.0 | Raw 256K ctx | - | "dark red"=3,483 LOOP. First recipient fixates |
| 0.7.1 | Anti-loop | - | Loop 99.2%↓. Personality emerged (Bob=spender, Alice=frugal) |
| 0.7.2 | Cafe economy | 1/3 | eat needs worker, work needs customer. Ghost worker bug fixed (not a.get("dead") worker filter) |
| 0.7.7 | Scarcity $10 | 0/3 | VERBAL cooperation emerged: "want to join me working?" |
| 0.7.9 | Diary v2 | 0/3 | Quality improved Day1→5. Notes in history, injected on wake |
| 0.7.10 | Diary v3 100-day | 3/3 | All died by Day9. Work loop bug (30+ turns alone) |
| 0.7.11 | Anti-loop fixes | 3/3 | All died by Day5. Bob died RICH ($174). [HOBBY] placeholder leak. Diary scrolls out of 25-msg history by afternoon |
Full Changelog: 0.6.2...0.7.13
0.6.2
- Add sleep pressure: nudge home >21:00
- Add diary inject-then-remove (reminder in, cleaned after, no buildup)
- Add "facts in context ARE memories" (softens Bob-denial)
v0.6.x (Loop Fixes)
| Ver | Change | Result |
|---|---|---|
| 0.6.1 | "Facts ARE memories" | Bob Denial: hard→soft ✅ |
| 0.6.2 | Sleep pressure@21h + diary injection/removal | STABLE ✅ |
| 0.6.4 | 100-Day test | Diary destroyed facts (31 summarizations). 0 JSON errors/105K lines |
Full Changelog: 0.5.9...0.6.2
0.5.9
- Fix example-persona placeholder leak -> concrete inert interest
- Fix Unicode (UTF-8)
- Stable Kooten-13B baseline:
v0.3.2–v0.5.9 (Kooten-13B Baseline)
| Ver | Score | Issue → Fix |
|---|---|---|
| 0.3.2 | 2/10 | "interesting" triggers hallucination |
| 0.4.6 | 3/10 | JSON cascade from malformed response |
| 0.5.1 | 6/10 | Clean baseline ✅ |
| 0.5.8 | 5/10 | "stamps" prompt bleeding → [HOBBY] placeholder |
| 0.5.9 | 6/10 | STABLE ✅ (~300 lines, 0 classes, 3 generic agents, no economy/death) |
Full Changelog: 0.5.8...0.5.9
0.5.8
- Add diary across days (long-term memory):
sleep-> note, morning reminder (inject_morning_diary()), auto-summarize >15 memos to resolve conflicts (manage_diary_entries()); e.g. bedtime{"response": "Today I first met Alice who likes coffee, Bob seems broke, tomorrow I'll explore the cafe", "action": "sleep"} - Add sleep cycle: at Home, after >20:00 or when the store closes = 8h sleep + reflection -> diary (
update_sleep_states()); encouraged not forced (no teleport-home, no fixed schedule) - Add multi-day:
current_time.day,run_simulation(num_days=2) - Add failed-action re-prompt with the reason (reflect: which step failed -> adjust / alt-strategy / abandon)
- Add examples -> "collecting stamps" ("outdoor" = stuck in park) +
[Speaker A]/[Speaker B] - Fix extractor to unwrap markdown fences
Full Changelog: 0.5.1...0.5.8
0.5.1
- Refactor to 1
send_message()for talk + Game-System (source = name or agent), kills dupsystem_interaction() - Unify prompt via
BASE_SYSTEM_PROMPT.format(name=...)(no dup string increate_agent()&clear_history()) - Add prompt rule:
response= inner monologue, reasons -> speech - Add history trim
MAX_HISTORY_MESSAGES=20+MAX_RESPONSE_TOKENS=100 - Simplify
extract_clean_json()(per-line, missingaction-> none) - Add API request
timeout=120s
Full Changelog: 0.4.6...0.5.1
0.4.6
- Add open-world
run_simulation(): autonomous turns, move locations, 1 action/step - Add world
LOCATIONS(Park/Home/Cafe) state machine +current_timeclock (advance_time()); swap the names = a different world, no pathfinding - Add
system_interaction(): Game-System asks an idle agent "what now?" - Add
extract_clean_json_for_history(): strip think-tags/prefix/fences ->{response,action}(keeps spoken text only, stops raw-history pollution + LLM self-imitation cascade) - Add
perform_action()+get_available_actions_for_agent()(go, talkTo, rest, sleep, wait, order) - Switch model -> llama-2-13b
- Note grind:
send_message()~=system_interaction()dup (killed in 0.5.1)
param($b) $b.Replace("nnFull Changelog", "n"+$fn+"n`nFull Changelog")
Full Changelog: 0.3.2...0.4.6
0.3.2
- Fix relay by stripping the
Name:label before handoff (response.split(":")inconv()) -> next agent gets clean text
Full Changelog: 0.3.1...0.3.2
0.3.1
- Fix duplicate speaker name on a line (
send_message()prepends only if absent) - Fix name to first line only via
first_utterance, not every reply - Revert 0.3.0 JSON
{response,action}-> plain conv
Full Changelog: 0.3.0...0.3.1