Terrarium in soul-protocol: reproduction with lineage - #313
Open
prakashUXtech wants to merge 4 commits into
Open
prakashUXtech wants to merge 4 commits into
prakashUXtech wants to merge 4 commits into
Conversation
A soul can now produce a child soul. `Soul.fork()` mints a new DID, records `parent_did` and bumps `generation`, deep-copies the DNA, and drifts each of the five OCEAN traits by an independent uniform delta clamped to 0..1. Lineage is a separate axis from reincarnation. `incarnation` and `previous_lives` track the same soul living again; `parent_did` and `generation` track a new soul descended from another. Both fields default, so souls written before this change load unchanged. Memory inheritance is opt-in per tier and defaults to core + procedural, because how-tos passing down is how culture spreads. Episodic is always empty on the child even when the caller asks for it. Bonds, role, the trust chain and the parent's mutation log stay with the parent. `EvolutionConfig.mutation_rate` has been serialized in every soul since the evolution system shipped and nothing read it. It is now the default drift width. Known consequence: `immutable_traits` defaults to `["personality", "core_values"]` and fork honours it, so a default-configured parent forks an OCEAN clone whatever the drift. `soul fork` warns and reports the frozen traits in `--json`; drop "personality" from the parent's `immutable_traits` to let children diverge. Adds `soul fork <parent> --child <name>` with --drift, --inherit, --charter, --output and --json. `soul inspect` and `soul status` show Parent and Generation for forked souls only. `public_profile()` carries the lineage.
reincarnate() built the new Identity without parent_did or generation, so a reborn child came back orphaned at generation 1. Lineage and rebirth are independent axes: a rebirth is a new life for the same soul, which is still its parent's child and still at the same depth in the family tree. Only the rebirth axis advances. Adds the case the fork tests missed — reincarnating the gen-1 parent made the assertions trivially true either way, so the drop went unnoticed. The new test forks, then reincarnates the child, and checks both axes. Also names origin_story and prime_directive in fork()'s does-not-inherit list; they were already excluded, just undocumented.
Issues (must fix)
Heads up
Please update your PR to address these points. |
Soul.birth() had no way to set EvolutionConfig, so a caller who wanted a soul whose children could diverge had to reach into soul._evolution.config after the fact. That matters because immutable_traits defaults to ["personality", "core_values"], and the personality category gates all five OCEAN traits — so a default-born soul forks into an exact OCEAN clone however large a drift is asked for. birth(evolution=...) accepts an EvolutionConfig or a plain dict and wires it into SoulConfig. Passing immutable_traits without "personality" is now the supported way to let a lineage diverge. Three tests: a default-born parent forks to a clone (the trap, pinned so it cannot regress silently), a dict-configured parent produces a child that drifts within mutation_rate, and an EvolutionConfig object is accepted as-is.
fork() and _drift_ocean() take rng: random.Random | None; a seeded instance makes the child's OCEAN drift reproducible while the DID stays fresh. The CLI exposes it as soul fork --seed N. Header and banner comments that tagged this work by the downstream project now say (lineage), the protocol-side name.
|
This PR has been automatically marked as stale because it has not had activity in the last 14 days. It will be closed in 7 days if no further activity occurs. If you're still working on this, please push an update or leave a comment. |
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.
Integration branch for Terrarium's soul-protocol work. Do not merge without review — this is the gate.
Terrarium is a watchable agent civilization: citizens are Souls that live in a world with scarce credits, build things, remember, and have children. Design and contract live in qbtrix/paw-workspace#207. Soul Protocol's part is reproduction.
What has landed here
Soul.fork()— a soul produces a child soul with lineagebirth(evolution=...)— so a lineage can actually divergeThe change
Soul.fork()mints a new DID for the child, recordsparent_did, bumpsgeneration, deep-copies the DNA, and drifts each OCEAN trait by an independent uniform delta clamped to 0..1. Memory inheritance defaults to core plus procedural, because how-tos passing down is how culture spreads; episodic is always empty on a child. Lineage is a separate axis from reincarnation, and both new fields default so existing souls load unchanged. Asoul forkCLI command exposes it with--json.Three things to look at
A bug this surfaced.
reincarnate()built its Identity without the lineage fields, so reincarnating a forked soul orphaned it at generation 1 and cut the family tree on every rebirth. Fixed with a test that forks, reincarnates the child, and checks both axes.Forking was genetically inert by default.
immutable_traitsdefaults to["personality", "core_values"], and the personality category gates all five OCEAN traits, so a default-born soul forked into an exact OCEAN clone whatever drift was asked for. Measured: a parent at openness 0.6 produced a child at exactly 0.6.So
birth()gained anevolutionparameter. It had no way to setEvolutionConfigat all, which meant a consumer had to reach intosoul._evolution.configfrom another repo. With the parameter, the same parent born with{"immutable_traits": ["core_values"], "mutation_rate": 0.08}produces a child at 0.5893. The clone case is pinned by a test so it cannot regress unnoticed.Whether drift-at-conception should have its own switch rather than inheriting the in-life mutation freeze is a real protocol question, and it wants an RFC rather than a decision buried here.
Verification
Full suite locally: 3258 to 3284 passed, the delta being exactly the 26 new tests in
tests/test_fork.py. Zero new failures. The six errors are pre-existing and environmental —tests/cross_runtime/conftest.pycallsasyncio.get_event_loop(), which raises on Python 3.14 while CI runs 3.11 to 3.13. I checked independently that this diff touches neither that directory nor any conftest.test_schemas.pyclean, so no schema regeneration was needed for the second change. Ruff check and format clean.Note on checks:
ci.ymltriggers on PRs intomainanddev, so the feature PRs into this integration branch ran only the quality gate. This PR is where the 3.11/3.12/3.13 matrix actually runs. Read the checks here, not there.Known downstream constraint
pocketpaw depends on the published
soul-protocol[engine]>=0.3.1, sobirth(evolution=...)will not reach it until a release. Becausebirth()only warns on unknown keyword arguments rather than raising, a consumer on an older version that passes it would get silently frozen traits. Terrarium's citizen seeding therefore verifies the resulting config rather than assuming the argument landed.Follow-up: seedable drift
Soul.fork()now takesrng: random.Random | None. When a caller passes a seeded generator, every OCEAN drift draw comes from it, so the same parent and seed give the same child traits; the DID is still minted fresh per call. With norngthe behaviour is exactly what it was. The CLI exposes it assoul fork --seed N. The header comments that tagged this work by the downstream project name now say(lineage), which is the protocol-side name for the feature. Suite: 3294 passed, 3 skipped on Python 3.12.