Skip to content

epic-3/story-2: Support Mutable Visitor State on Contexts - #34

Closed
usmanabbas7 wants to merge 5 commits into
epic-3/story-1-add-the-persistence-boundary-and-in-memory-storefrom
epic-3/story-2-support-mutable-visitor-state-on-contexts
Closed

epic-3/story-2: Support Mutable Visitor State on Contexts#34
usmanabbas7 wants to merge 5 commits into
epic-3/story-1-add-the-persistence-boundary-and-in-memory-storefrom
epic-3/story-2-support-mutable-visitor-state-on-contexts

Conversation

@usmanabbas7

Copy link
Copy Markdown
Collaborator

Story 3.2: Support Mutable Visitor State on Contexts

Part of sprint sprint/2026-04-06-convert-python-sdk. Stacked on epic-3/story-1 (#33#32#31#30#29#24).

Adds the public Context.set_attributes(attributes: dict[str, Any]) -> None mutation surface on top of the Story 1.3 Context/ContextState foundation and the Story 3.1 DataStore persistence boundary. Honors audit finding F-008.

What was built

  • domain/context_state.py — immutable ContextState.with_attributes(...) returning a merged copy (frozen, L0-clean; no in-place mutation).
  • context.pyContext.set_attributes(...) rebinds ContextState to the merged copy and persists it through the injected DataStore (_persist_visitor_state()); request-time overlay seam (_resolve_visitor_attributes) left unchanged and distinct (overlay > persisted-state precedence).
  • core.pycreate_context now hydrates per-visitor ContextState from the store (_hydrate_visitor_attributes) and injects the single per-Core DataStore into the context seam, so a fresh create_context(visitor_id) rehydrates the persisted update (AC#3).
  • ports/storage.py — added stable visitor-scoped visitor_state_key() (mirrors the dedup: namespacing); no second store, no parallel dict, no second key scheme.
  • JS parity = Context.updateVisitorProperties_dataManager.putData(visitorId, {segments}) (F-008-corrected anchor).

Audit finding honored

  • F-008 — update applies by replacing ContextState with a merged copy persisted through the DataStore (corrected from the stale getVisitorProperties-only deep-merge framing). Baked into the spec; verified in code.

Driver-resolved precedence (auto-delegated, sprint mode)

  • Story 3.1 did NOT actually wire ContextState hydrate/save in create_context (only tracking/deduplication.py used the store) — minimal hydrate/save wiring added through the SAME single per-Core DataStore and a new state:<visitor_id> key. Logged as a "your call" auto-delegation.
  • The stale "working tree effectively empty" story note was ignored; full shipped 1.1–3.1 code was extended.
  • Layering enforced via the existing static-scan tests/test_layering.py (import-linter not installed in MVP toolchain); context.py depends only on the ports/storage.py protocol.

Beads

Epic ai-driven-product-dev-u4mw; tasks -vi7i, -ni5e, -xuci — all closed.

Tests

396 → 415 (+19), zero regressions. Extended flat tests/test_context_creation.py (unit) + tests/integration/test_in_memory_store.py (qs-06 round-trip).

Sprint-mode notes

  • Readiness gate: PASS 8.6/10, 3 questions auto-delegated ("your call"). See conductor's work/2026-06-08-support-mutable-visitor-state-on-contexts/readiness-assessment.md.
  • Code review: clean, 1 round, no warnings.

🤖 Generated with Claude Code

@usmanabbas7 usmanabbas7 self-assigned this Jun 8, 2026
@usmanabbas7
usmanabbas7 requested a review from clllaur June 8, 2026 08:14

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements mutable visitor state persistence (Story 3.2) by introducing Context.set_attributes() to persistently update and merge visitor attributes via an injected DataStore, and rehydrating these attributes during context creation in Core. The review feedback suggests several performance and API flexibility improvements: typing the attributes parameter as Mapping[str, Any] instead of dict[str, Any] for better flexibility, and avoiding unnecessary dictionary copying/object instantiation in ContextState.with_attributes() and Core._hydrate_visitor_attributes() when no new attributes are provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.


# --- mutable visitor state (Story 3.2) ---------------------------------

def set_attributes(self, attributes: dict[str, Any]) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using Mapping[str, Any] instead of dict[str, Any] for the attributes parameter is more Pythonic and flexible. It allows callers to pass other mapping types (such as MappingProxyType or custom read-only dictionaries) and aligns with the type hints used elsewhere in the SDK (e.g., visitor_attributes is typed as Optional[Mapping[str, Any]]).

Suggested change
def set_attributes(self, attributes: dict[str, Any]) -> None:
def set_attributes(self, attributes: Mapping[str, Any]) -> None:

Comment on lines +98 to +105
merged = dict(self.visitor_attributes)
if new_attributes:
merged.update(new_attributes)
return ContextState(
visitor_id=self.visitor_id,
snapshot=self.snapshot,
visitor_attributes=merged,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If new_attributes is empty or None, we can return self directly. Since ContextState is a frozen dataclass, returning self is perfectly safe, avoids unnecessary dictionary copying, and prevents creating a new ContextState instance.

Suggested change
merged = dict(self.visitor_attributes)
if new_attributes:
merged.update(new_attributes)
return ContextState(
visitor_id=self.visitor_id,
snapshot=self.snapshot,
visitor_attributes=merged,
)
if not new_attributes:
return self
merged = dict(self.visitor_attributes)
merged.update(new_attributes)
return ContextState(
visitor_id=self.visitor_id,
snapshot=self.snapshot,
visitor_attributes=merged,
)

Comment thread src/convert_sdk/core.py
Comment on lines +166 to +169
merged = dict(stored)
if visitor_attributes:
merged.update(visitor_attributes)
return merged

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If visitor_attributes is empty or None, we can return stored directly. This avoids an unnecessary dictionary copy when there are no caller-supplied attributes to merge with the persisted state.

Suggested change
merged = dict(stored)
if visitor_attributes:
merged.update(visitor_attributes)
return merged
if not visitor_attributes:
return stored
merged = dict(stored)
merged.update(visitor_attributes)
return merged

usmanabbas7 and others added 5 commits June 14, 2026 21:59
…ibutes — tests (RED)

Beads: ai-driven-product-dev-vi7i

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ibutes — implementation (GREEN)

Beads: ai-driven-product-dev-vi7i

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RED)

Beads: ai-driven-product-dev-ni5e

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_context — implementation (GREEN)

Beads: ai-driven-product-dev-ni5e

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n round-trip — tests (verification)

Beads: ai-driven-product-dev-xuci
Verification task (Task 4): no production code change; proves FR25 determinism,
AC#5 overlay/persisted distinction, AC#2/#3 round-trip via qs-06 fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@usmanabbas7
usmanabbas7 force-pushed the epic-3/story-1-add-the-persistence-boundary-and-in-memory-store branch from c413d52 to 52f5a08 Compare June 14, 2026 16:59
@usmanabbas7
usmanabbas7 force-pushed the epic-3/story-2-support-mutable-visitor-state-on-contexts branch from c9d9d13 to 2298a37 Compare June 14, 2026 16:59
@abbaseya

Copy link
Copy Markdown
Collaborator

Superseded — all commits already in main (bc76b64). Closing without merge as part of post-sprint cleanup.

@abbaseya abbaseya closed this Jun 18, 2026
@abbaseya
abbaseya deleted the epic-3/story-2-support-mutable-visitor-state-on-contexts branch June 18, 2026 16:31
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.

2 participants