Skip to content

Releases: PsychoSmiley/NPCAgent

0.9.2

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57

Sim (npc.py)

  • Add reproduction: in-conversation askSexPossibleChild -> the partner's next menu carries acceptSexPossibleChild (any other action = silent decline, the refusal lives in their words); an accepted pair is expecting, due in GESTATION_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 the ADULT_AGE menu 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 talkTo first-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 talkTo self-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_think against 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 = 8 config (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_notes returned diary[-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 reads D1-D19 instead of D10-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.py change.

Sim (npc.py) - clock and sleep

  • Fix a crash that ended runs permanently: sleep_remaining_hours decrements by 0.5, so the all-asleep jump could hand advance_time a float, making every clock field a float until format_time's :02d raised.
  • 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 none or leaveConversation expires 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_json dying 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 \u2014 instead.
  • Refuse goX to your current location (it silently dropped a cafe shift) and leaveConversation outside 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 conversations column, an array yields N rows.
  • Add a Codex quota readout on the Engine control: remaining percent in the option, a drain-fill tint...
Read more

0.8.1

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57

Sim (npc.py)

  • Add player-as-actor: --player[=Name] -> a human joins as an agent (default PersonX, 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_YEAR drives the "1 year = N weeks" prompt line (28 -> "4 weeks", 1 -> "1 day").
  • Add REASONING_ENABLED flag: 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 eat to pay the single enforced worker (was random.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_json returns a dict and coerces a non-str response (a {"response":5} used to kill the run).
  • Fix money to a scalar via create_agent(money=) (no name-keyed .get); show E/{100-age}; clamp the diary bedtime note.

Browser harness (index.html)

  • Add the in-browser harness: runs npc.py via 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.
v0 8 0_tom-timeline

# 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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • 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 on leaveConversation)

  • 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 none slowly burns energy

  • Add QoL: SHOW_SUMMARY consolidation 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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • 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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • 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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • 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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • Refactor to 1 send_message() for talk + Game-System (source = name or agent), kills dup system_interaction()
  • Unify prompt via BASE_SYSTEM_PROMPT.format(name=...) (no dup string in create_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, missing action -> none)
  • Add API request timeout=120s

Full Changelog: 0.4.6...0.5.1

0.4.6

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • Add open-world run_simulation(): autonomous turns, move locations, 1 action/step
  • Add world LOCATIONS (Park/Home/Cafe) state machine + current_time clock (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

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • Fix relay by stripping the Name: label before handoff (response.split(":") in conv()) -> next agent gets clean text

Full Changelog: 0.3.1...0.3.2

0.3.1

Choose a tag to compare

@PsychoSmiley PsychoSmiley released this 02 Aug 11:57
  • 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