Skip to content

Feat/v1.1 final polish#114

Merged
spuentesp merged 6 commits into
masterfrom
feat/v1.1-final-polish
Jun 26, 2026
Merged

Feat/v1.1 final polish#114
spuentesp merged 6 commits into
masterfrom
feat/v1.1-final-polish

Conversation

@spuentesp

@spuentesp spuentesp commented Jun 26, 2026

Copy link
Copy Markdown
Owner

v1.1 Final Polish

This PR merges the v1.1-final-polish branch — the work that turned the
v1.1.0 release into a shippable system.

Highlights

  • NPC + Character Version system — per-universe incarnations, no
    cross-incarnation leak. Includes NPCProfile generation prompt, the
    include_cross_incarnation flag for opt-in cross-universe recall,
    and the CharacterCard import / expansion path.
  • Character Conversations — DIRECT / ACTOR modes, in-character
    dialogues, relationship deltas, the full NPCVoice pipeline.
  • WebSocket phase events — additive {type:"phase"} events
    before start; "Recalling the world… / Rolling… / Writing…"
    surfacing for the UI's ThinkingIndicator.
  • GM Co-Pilot (CF-2..CF-7) — recap, plot hooks, contradictions,
    session prep, handout generation, story threads.
  • World Forge bootstrapquick-world and demo-world paths.
  • Live E2E replay suitescripts/run_e2e_replays.py and the
    per-suite scripts (forge_replay, long_form_narration,
    subsystem_replay, character_creation_replay) + docs in
    docs/testing/E2E_REPLAY.md.
  • Backend fixes surfaced by the suite:
    • CharacterCreationProcedure schema: added legacy enum aliases
      (choose_name, choose_attributes, choose_disciplines,
      choose_advantages) so existing bundles load.
    • neo4j_create_fact / neo4j_create_event / neo4j_update_fact:
      flatten nested Map properties to JSON strings before writing to
      Neo4j (Cypher rejects non-primitive property values).

Codex review (all four findings resolved)

  • P1 core.pyfork_universe / split_universe / merge_universes
    matched (u)-[:IN_MULTIVERSE]->(mv) but the schema creates
    (m)-[:CONTAINS]->(u). Queries matched zero rows. Fixed: flipped
    to (mv)-[:CONTAINS]->(u).
  • P1 character_conversation.py_provision_entity_and_profile
    called neo4j_create_entity directly, bypassing the CanonKeeper
    authority check (INV-1). Fixed: routes through
    CanonKeeper.create_entity.
  • P2 conversation_loop.pyConversationState did not declare
    include_cross_incarnation; assignments were silently stripped.
    Fixed: added the field with a False default.
  • P2 npc_voice.py_recall_memories filtered by universe_id
    even when include_cross_incarnation=True. Fixed: drops the
    universe scope when cross-incarnation recall is requested.

Use-case traceability

Every change references a use-case ID (DL-*, P-*, M-*, CF-*,
DOC-*). New use-cases documented under
docs/use-cases/epic-9-docs-DOC/DOC-1/.

Test coverage

  • 580+ lines of new universe-scoping tests for the character-version
    system (test_npc_voice_universe_scoping*).
  • 245 lines of new character-conversation router tests.
  • 107 lines of new chat-WS phase-event contract tests.
  • 169 lines of new conversation-loop e2e tests.

Files changed

60 files, ~3,500 lines added. See Files Changed tab.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd7700c245

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


client.execute_write(
"""
MATCH (source:Universe {id: $source_uid})-[:IN_MULTIVERSE]->(mv:Multiverse)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the existing CONTAINS edge for split/merge universes

For universes created by the standard neo4j_create_universe path, the graph links (m:Multiverse)-[:CONTAINS]->(u:Universe), not (u)-[:IN_MULTIVERSE]->(mv). In that normal environment this MATCH produces no row, so split creates no target universe and subsequent clone writes match nothing while still reporting cloned counts; the merge query repeats the same edge pattern and fails for the same standard universes.

Useful? React with 👍 / 👎.

from monitor_data.tools.mongodb_tools import mongodb_create_npc_profile
from monitor_data.tools.neo4j_tools.entities import neo4j_create_entity

entity = neo4j_create_entity(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route character expansion writes through CanonKeeper

This new expansion path writes an Entity directly from the UI backend, bypassing the MCP authority middleware that restricts neo4j_create_entity to CanonKeeper and the repo rule that only CanonKeeper writes to Neo4j. Any first chat/expand of a standalone character now creates canon graph state outside the ProposedChange/CanonKeeper review path, so this should go through CanonKeeper or stage a proposal instead.

Useful? React with 👍 / 👎.

# the cross-incarnation flag by setting a transient attribute on the
# loop state (read by NPCVoice via the conversation_loop wrapper).
if include_cross_incarnation and getattr(loop, "state", None) is not None:
loop.state.include_cross_incarnation = True # type: ignore[attr-defined]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare the cross-incarnation flag before setting it

When the UI sends with “Remember across incarnations” enabled, this assignment runs against the real ConversationState Pydantic model from ConversationLoop.start; that model does not define include_cross_incarnation, so assigning the unknown field raises before loop.step() runs and the send endpoint returns an error. Add the flag to ConversationState or pass it through an explicit method argument.

Useful? React with 👍 / 👎.

if universe_id is not None:
search_kwargs["universe_id"] = str(universe_id)
if include_cross_incarnation:
search_kwargs["include_cross_incarnation"] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Broaden recall by removing the universe filter

Even if the cross-incarnation send path reaches NPCVoice, _recall_memories still includes universe_id and only adds include_cross_incarnation; the data-layer MemorySearchRequest/qdrant_search_memories does not define or use that flag and continues to filter by the current universe. In the opt-in case this means recall remains scoped to one incarnation, so either omit universe_id when this flag is true or add real data-layer support for it.

Useful? React with 👍 / 👎.

Alumno and others added 5 commits June 25, 2026 21:26
…eak)

NPCVoice + ConversationLoop now thread universe_id through recall,
memory writes, working state, and staged proposals. Each character card
holds a versions[] array; every incarnation has its own entity_id +
NPCProfile + memory set + relationship deltas, partitioned by universe.

- data-layer: NPCProfile gains universe_id, relationship_states_by_universe,
  current_emotional_state_by_universe (legacy fields remain as fallback).
- agents: NPCVoice._recall_memories/_write_npc_memory/_build_*_snapshot
  read/write the per-universe partition first; add universe_id +
  include_cross_incarnation kwargs to respond_direct. ConversationLoop
  stamps universe_id on staged ProposedChanges.
- UI backend: character_storage.add_version/get_version/list_versions/
  touch_version/delete_version; character_conversation.ensure_character_backed
  is per-universe idempotent; new delete_incarnation tears down entity +
  profile. 5 new endpoints (POST/GET/DELETE /versions + universe_id +
  include_cross_incarnation flags).
- frontend: StandaloneCharacter + ConversationStart carry versions /
  default_universe_id / version_id / universe_id. Roster shows Nx badge,
  editor has Incarnations panel (list + delete + add by universe), chat
  has 'Remember across incarnations' toggle.
- tests: 13 NPCVoice universe-scoping tests, 3 isolation tests in
  test_character_conversation, 9 frontend version-API tests; 87 agent
  tests, 39 backend tests, 38 frontend tests, type-check clean.
- docs: docs/use-cases/epic-3-identity-M/M-31/CHARACTER_VERSIONS.md
  describes the architecture, data model, endpoint catalog, migration.
  docs/USE_CASES.md adds a VERSIONS row pointing at it.

Use cases: M-31, DL-20, P-17, P-20.

Co-Authored-By: Claude <noreply@anthropic.com>
@spuentesp
spuentesp force-pushed the feat/v1.1-final-polish branch from bd7700c to e5f1282 Compare June 26, 2026 01:29
P1 — packages/data-layer/src/monitor_data/tools/neo4j_tools/core.py
  Fork/split/merge Cypher matched (u)-[:IN_MULTIVERSE]->(mv) but the
  schema (core.py:72 / :314) creates (m)-[:CONTAINS]->(u) — opposite
  direction. Queries matched zero rows, so fork/split/merge silently
  failed. Flipped all three to (mv)-[:CONTAINS]->(source/new).

P1 — packages/ui/backend/src/monitor_ui/routers/character_conversation.py
  _provision_entity_and_profile called neo4j_create_entity directly,
  bypassing the CanonKeeper authority check (INV-1: only CanonKeeper
  writes Neo4j entity nodes). Route through CanonKeeper.create_entity.

P2 — packages/agents/src/monitor_agents/loops/conversation_loop.py
  ConversationState did not declare include_cross_incarnation, so
  assignments to state.include_cross_incarnation were silently dropped.
  Added the field with a False default (preserves per-universe
  incarnation isolation).

P2 — packages/agents/src/monitor_agents/npc_voice.py
  _recall_memories filtered by universe_id even when
  include_cross_incarnation=True, making the flag a no-op. Drop the
  universe scope when cross-incarnation recall is requested.

Co-Authored-By: Claude <noreply@anthropic.com>
@spuentesp
spuentesp merged commit 8042665 into master Jun 26, 2026
1 check passed
@spuentesp
spuentesp deleted the feat/v1.1-final-polish branch June 26, 2026 02:18
@spuentesp
spuentesp restored the feat/v1.1-final-polish branch July 25, 2026 00:13
@spuentesp
spuentesp deleted the feat/v1.1-final-polish branch July 25, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant