mem01session 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ """OpenAI Agents SDK embedded session adapter for mem01."""
2
+
3
+ from .metrics import ContextEstimate, estimate_prompt_context
4
+ from .runtime import (
5
+ EmbeddedMem01Runtime,
6
+ SharedRuntimeLease,
7
+ acquire_shared_runtime,
8
+ close_shared_runtimes,
9
+ )
10
+ from .session import Mem01MemoryError, Mem01Session
11
+
12
+ memSession = Mem01Session
13
+
14
+ __all__ = [
15
+ "ContextEstimate",
16
+ "EmbeddedMem01Runtime",
17
+ "Mem01Session",
18
+ "Mem01MemoryError",
19
+ "memSession",
20
+ "SharedRuntimeLease",
21
+ "acquire_shared_runtime",
22
+ "close_shared_runtimes",
23
+ "estimate_prompt_context",
24
+ ]
25
+ __version__ = "0.1.0"
mem01session/demo.py ADDED
@@ -0,0 +1,614 @@
1
+ """Deterministic three-lane evidence demo and optional live smoke test."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import copy
8
+ import json
9
+ import os
10
+ from collections.abc import AsyncIterator, Mapping, Sequence
11
+ from dataclasses import dataclass
12
+ from datetime import UTC, datetime
13
+ from importlib import metadata, resources
14
+ from pathlib import Path
15
+ from tempfile import TemporaryDirectory
16
+ from typing import Any, cast
17
+ from uuid import uuid4
18
+
19
+ from agents import Agent, Model, RunConfig, Runner, SQLiteSession
20
+ from agents.items import ModelResponse
21
+ from agents.usage import Usage
22
+ from mem01 import ApplyResult
23
+ from mem01 import env as mem01_env
24
+ from mem01.types import (
25
+ Belief,
26
+ BeliefSource,
27
+ BeliefStatus,
28
+ PackedMemory,
29
+ ScopeIds,
30
+ )
31
+ from openai.types.responses import ResponseOutputMessage, ResponseOutputText
32
+
33
+ from .items import text_from_item
34
+ from .metrics import (
35
+ OFFLINE_PREPARED_INPUT_MEASUREMENT,
36
+ TOKEN_ESTIMATOR_UTF8_BYTES_UPPER_BOUND,
37
+ estimate_prompt_context,
38
+ )
39
+ from .session import Mem01Session
40
+
41
+ USER_ID = "build-week-user"
42
+ LIVE_MODEL = "gpt-5.6-sol"
43
+ ANSWER_MODEL = "local-deterministic-fake"
44
+ EXTRACTION_MODEL = "local-deterministic-fixture-extractor"
45
+ MAX_MEMORY_TOKENS = 800
46
+ TOKEN_ESTIMATOR = TOKEN_ESTIMATOR_UTF8_BYTES_UPPER_BOUND
47
+ CHECKPOINTS = (1, 10, 40)
48
+ ARTIFACT_PATH = Path(__file__).parents[2] / "artifacts" / "prepared-input-scaling.json"
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class DemoConversation:
53
+ """One immutable input from the checked-in Build Week scenario."""
54
+
55
+ conversation: int
56
+ user_input: str
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class DeterministicDemoResult:
61
+ """Auditable prepared-state evidence; outputs are observations only."""
62
+
63
+ prepared_inputs: dict[str, dict[int, list[Any]]]
64
+ observed_answers: dict[str, dict[int, str]]
65
+ beliefs: list[Belief]
66
+ mem01_session_ids: tuple[str, ...]
67
+ mem01_user_ids: tuple[str, ...]
68
+ sdk_runs: int
69
+
70
+
71
+ def load_conversation_fixture() -> tuple[DemoConversation, ...]:
72
+ """Load the packaged, checked-in 40-conversation fixture."""
73
+ fixture = resources.files("mem01session").joinpath(
74
+ "fixtures/build_week_conversations.json"
75
+ )
76
+ raw = json.loads(fixture.read_text(encoding="utf-8"))
77
+ return tuple(
78
+ DemoConversation(
79
+ conversation=int(record["conversation"]),
80
+ user_input=str(record["user_input"]),
81
+ )
82
+ for record in raw
83
+ )
84
+
85
+
86
+ def _stored_at(day: int) -> datetime:
87
+ return datetime(2026, 7, day, 12, tzinfo=UTC)
88
+
89
+
90
+ class _DeterministicMemoryRuntime:
91
+ """Small local runtime that models only fixture facts, with no I/O."""
92
+
93
+ def __init__(self) -> None:
94
+ scope_ids = ScopeIds(user_id=USER_ID)
95
+ self._beliefs = [
96
+ Belief(
97
+ id="fixture-location-nyc",
98
+ content="Lives in New York City",
99
+ status=BeliefStatus.SUPERSEDED,
100
+ scope_ids=scope_ids,
101
+ source=BeliefSource.EXTRACTION,
102
+ created_at=_stored_at(1),
103
+ updated_at=_stored_at(2),
104
+ ),
105
+ Belief(
106
+ id="fixture-rent",
107
+ content="Monthly rent was $2,400",
108
+ scope_ids=scope_ids,
109
+ source=BeliefSource.EXTRACTION,
110
+ created_at=_stored_at(1),
111
+ updated_at=_stored_at(1),
112
+ ),
113
+ Belief(
114
+ id="fixture-location-sf",
115
+ content="Lives in San Francisco",
116
+ scope_ids=scope_ids,
117
+ source=BeliefSource.EXTRACTION,
118
+ created_at=_stored_at(2),
119
+ updated_at=_stored_at(2),
120
+ supersedes_id="fixture-location-nyc",
121
+ ),
122
+ ]
123
+ self._seen_first_fact = False
124
+ self._seen_move = False
125
+
126
+ def _visible_beliefs(self) -> list[Belief]:
127
+ visible: list[Belief] = []
128
+ for belief in self._beliefs:
129
+ if belief.id in {"fixture-location-nyc", "fixture-rent"}:
130
+ if self._seen_first_fact:
131
+ visible.append(belief)
132
+ elif self._seen_move:
133
+ visible.append(belief)
134
+ return visible
135
+
136
+ async def remember(
137
+ self,
138
+ messages: list[dict[str, str]],
139
+ *,
140
+ user_id: str,
141
+ session_id: str | None = None,
142
+ ) -> object:
143
+ del session_id
144
+ if user_id != USER_ID:
145
+ raise ValueError("deterministic demo user mismatch")
146
+ user_text = "\n".join(
147
+ message["content"] for message in messages if message["role"] == "user"
148
+ )
149
+ if "NYC" in user_text and "$2,400" in user_text:
150
+ self._seen_first_fact = True
151
+ if "moved to SF" in user_text:
152
+ self._seen_move = True
153
+ return type("RememberResult", (), {"apply": ApplyResult()})()
154
+
155
+ async def recall(
156
+ self,
157
+ query: str,
158
+ *,
159
+ user_id: str,
160
+ max_memory_tokens: int,
161
+ include_history: bool = False,
162
+ ) -> PackedMemory:
163
+ del query, include_history
164
+ if user_id != USER_ID:
165
+ raise ValueError("deterministic demo user mismatch")
166
+ beliefs = self._visible_beliefs()
167
+ return PackedMemory(
168
+ beliefs=beliefs,
169
+ text="\n".join(belief.content for belief in beliefs),
170
+ tokens_used=0,
171
+ max_memory_tokens=max_memory_tokens,
172
+ candidate_count=len(beliefs),
173
+ latency_ms=0,
174
+ )
175
+
176
+ async def history(
177
+ self,
178
+ *,
179
+ user_id: str,
180
+ include_invalidated: bool = False,
181
+ limit: int = 100,
182
+ ) -> list[Belief]:
183
+ if user_id != USER_ID:
184
+ raise ValueError("deterministic demo user mismatch")
185
+ beliefs = self._visible_beliefs()
186
+ if not include_invalidated:
187
+ beliefs = [
188
+ belief for belief in beliefs if belief.status == BeliefStatus.ACTIVE
189
+ ]
190
+ return beliefs[-limit:]
191
+
192
+
193
+ class _DeterministicModel(Model):
194
+ """Local model used only to drive the real Agent/Runner preparation path."""
195
+
196
+ def __init__(self) -> None:
197
+ self.prepared_inputs: list[list[Any]] = []
198
+
199
+ @staticmethod
200
+ def _answer(input_items: Sequence[Any]) -> str:
201
+ texts = [
202
+ text for item in input_items if (text := text_from_item(item)) is not None
203
+ ]
204
+ current = texts[-1].lower() if texts else ""
205
+ context = "\n".join(texts)
206
+ if "sister" in current:
207
+ return "I do not have your sister's name stored."
208
+ if "where do i live" in current:
209
+ if "San Francisco" in context and "$2,400" in context:
210
+ return "You live in San Francisco; your prior rent was $2,400."
211
+ has_location = "moved to SF" in context or "live in NYC" in context
212
+ if has_location and "$2,400" in context:
213
+ return "The available conversation contains location and rent details."
214
+ return "I do not have that personal information stored."
215
+ return "Noted."
216
+
217
+ async def get_response(
218
+ self,
219
+ system_instructions: str | None,
220
+ input: Any,
221
+ *args: Any,
222
+ **kwargs: Any,
223
+ ) -> ModelResponse:
224
+ del system_instructions, args, kwargs
225
+ prepared = copy.deepcopy(list(input))
226
+ self.prepared_inputs.append(prepared)
227
+ answer = self._answer(prepared)
228
+ output = ResponseOutputMessage(
229
+ id=f"deterministic-message-{len(self.prepared_inputs)}",
230
+ content=[
231
+ ResponseOutputText(
232
+ annotations=[],
233
+ text=answer,
234
+ type="output_text",
235
+ logprobs=[],
236
+ )
237
+ ],
238
+ role="assistant",
239
+ status="completed",
240
+ type="message",
241
+ )
242
+ return ModelResponse(
243
+ output=[output],
244
+ usage=Usage(requests=1, input_tokens=0, output_tokens=0, total_tokens=0),
245
+ response_id=f"deterministic-response-{len(self.prepared_inputs)}",
246
+ )
247
+
248
+ async def stream_response(
249
+ self,
250
+ system_instructions: str | None,
251
+ input: Any,
252
+ *args: Any,
253
+ **kwargs: Any,
254
+ ) -> AsyncIterator[Any]:
255
+ del system_instructions, input, args, kwargs
256
+ if False:
257
+ yield None
258
+ raise AssertionError("deterministic demo does not stream")
259
+
260
+
261
+ def _agent(model: Model) -> Agent[Any]:
262
+ return Agent(
263
+ name="Mem01Session deterministic evidence agent",
264
+ instructions=(
265
+ "Use only prepared conversation or memory. Treat model outputs as "
266
+ "observations, not test guarantees."
267
+ ),
268
+ model=model,
269
+ )
270
+
271
+
272
+ async def _run_one(
273
+ model: _DeterministicModel,
274
+ prompt: str,
275
+ session: Any,
276
+ *,
277
+ run_config: RunConfig,
278
+ ) -> tuple[list[Any], str]:
279
+ result = await Runner.run(
280
+ _agent(model),
281
+ prompt,
282
+ session=session,
283
+ run_config=run_config,
284
+ )
285
+ return copy.deepcopy(model.prepared_inputs[-1]), str(result.final_output)
286
+
287
+
288
+ async def run_deterministic_demo(
289
+ *,
290
+ max_memory_tokens: int = MAX_MEMORY_TOKENS,
291
+ ) -> DeterministicDemoResult:
292
+ """Run 40 inputs through three real SDK preparation strategies, key-free."""
293
+ fixture = load_conversation_fixture()
294
+ snapshots: dict[str, dict[int, list[Any]]] = {
295
+ "fresh_stock": {},
296
+ "reused_stock": {},
297
+ "mem01session": {},
298
+ }
299
+ observations: dict[str, dict[int, str]] = {
300
+ "fresh_stock": {},
301
+ "reused_stock": {},
302
+ "mem01session": {},
303
+ }
304
+ memory_ids: list[str] = []
305
+ memory_users: list[str] = []
306
+
307
+ with TemporaryDirectory(prefix="mem01session-deterministic-") as directory:
308
+ root = Path(directory)
309
+ fresh_model = _DeterministicModel()
310
+ for turn in fixture:
311
+ session = SQLiteSession(
312
+ f"fresh-stock-{turn.conversation}",
313
+ db_path=root / "fresh-stock.db",
314
+ )
315
+ try:
316
+ prepared, answer = await _run_one(
317
+ fresh_model,
318
+ turn.user_input,
319
+ session,
320
+ run_config=RunConfig(tracing_disabled=True),
321
+ )
322
+ finally:
323
+ session.close()
324
+ if turn.conversation in CHECKPOINTS:
325
+ snapshots["fresh_stock"][turn.conversation] = prepared
326
+ observations["fresh_stock"][turn.conversation] = answer
327
+
328
+ reused_model = _DeterministicModel()
329
+ reused_session = SQLiteSession(
330
+ "reused-stock",
331
+ db_path=root / "reused-stock.db",
332
+ )
333
+ try:
334
+ for turn in fixture:
335
+ prepared, answer = await _run_one(
336
+ reused_model,
337
+ turn.user_input,
338
+ reused_session,
339
+ run_config=RunConfig(tracing_disabled=True),
340
+ )
341
+ if turn.conversation in CHECKPOINTS:
342
+ snapshots["reused_stock"][turn.conversation] = prepared
343
+ observations["reused_stock"][turn.conversation] = answer
344
+ finally:
345
+ reused_session.close()
346
+
347
+ runtime = _DeterministicMemoryRuntime()
348
+ memory_model = _DeterministicModel()
349
+ for turn in fixture:
350
+ session_id = f"mem01-conversation-{turn.conversation}"
351
+ memory_ids.append(session_id)
352
+ memory_users.append(USER_ID)
353
+ memory_session = Mem01Session(
354
+ session_id,
355
+ USER_ID,
356
+ runtime=runtime, # type: ignore[arg-type]
357
+ conversation_db=root / f"memory-{turn.conversation}.db",
358
+ max_memory_tokens=max_memory_tokens,
359
+ strict=True,
360
+ )
361
+ try:
362
+ config = memory_session.run_config()
363
+ config.tracing_disabled = True
364
+ prepared, answer = await _run_one(
365
+ memory_model,
366
+ turn.user_input,
367
+ memory_session,
368
+ run_config=config,
369
+ )
370
+ finally:
371
+ await memory_session.close()
372
+ if turn.conversation in CHECKPOINTS:
373
+ snapshots["mem01session"][turn.conversation] = prepared
374
+ observations["mem01session"][turn.conversation] = answer
375
+
376
+ beliefs = await runtime.history(
377
+ user_id=USER_ID,
378
+ include_invalidated=True,
379
+ limit=100,
380
+ )
381
+
382
+ return DeterministicDemoResult(
383
+ prepared_inputs=snapshots,
384
+ observed_answers=observations,
385
+ beliefs=beliefs,
386
+ mem01_session_ids=tuple(memory_ids),
387
+ mem01_user_ids=tuple(memory_users),
388
+ sdk_runs=len(fixture) * 3,
389
+ )
390
+
391
+
392
+ def generate_artifact_records(
393
+ result: DeterministicDemoResult,
394
+ *,
395
+ max_memory_tokens: int = MAX_MEMORY_TOKENS,
396
+ ) -> list[dict[str, Any]]:
397
+ """Create generated offline measurements at conversations 1, 10, and 40."""
398
+ agents_version = metadata.version("openai-agents")
399
+ records: list[dict[str, Any]] = []
400
+ for strategy in ("fresh_stock", "reused_stock", "mem01session"):
401
+ for conversation in CHECKPOINTS:
402
+ estimate = estimate_prompt_context(
403
+ result.prepared_inputs[strategy][conversation]
404
+ )
405
+ records.append(
406
+ {
407
+ "strategy": strategy,
408
+ "conversation": conversation,
409
+ "prepared_input_items": estimate.item_count,
410
+ "prepared_input_characters": estimate.text_characters,
411
+ "estimated_tokens": estimate.estimated_tokens,
412
+ "measurement": OFFLINE_PREPARED_INPUT_MEASUREMENT,
413
+ "token_estimator": TOKEN_ESTIMATOR,
414
+ "openai_agents_version": agents_version,
415
+ "answer_model": ANSWER_MODEL,
416
+ "extraction_model": EXTRACTION_MODEL,
417
+ "max_memory_tokens": max_memory_tokens,
418
+ }
419
+ )
420
+ return records
421
+
422
+
423
+ def render_artifact(records: Sequence[Mapping[str, Any]]) -> str:
424
+ """Render the canonical generated JSON artifact."""
425
+ return json.dumps(list(records), indent=2, ensure_ascii=False) + "\n"
426
+
427
+
428
+ async def write_artifact(path: Path = ARTIFACT_PATH) -> Path:
429
+ """Regenerate the artifact from a fresh isolated deterministic run."""
430
+ result = await run_deterministic_demo()
431
+ content = render_artifact(generate_artifact_records(result))
432
+ await asyncio.to_thread(_write_text, path, content)
433
+ return path
434
+
435
+
436
+ def _write_text(path: Path, content: str) -> None:
437
+ """Write generated output from a worker when called by async entry points."""
438
+ path.parent.mkdir(parents=True, exist_ok=True)
439
+ path.write_text(content, encoding="utf-8")
440
+
441
+
442
+ def report_as_dict(result: DeterministicDemoResult) -> dict[str, Any]:
443
+ """Return stable human-facing evidence without serializing raw SDK items."""
444
+ return {
445
+ "mode": "deterministic-key-free",
446
+ "sdk_runs": result.sdk_runs,
447
+ "fixture_conversations": len(load_conversation_fixture()),
448
+ "measurements": generate_artifact_records(result),
449
+ "observed_answers": result.observed_answers,
450
+ "belief_lifecycle": [
451
+ {"id": belief.id, "content": belief.content, "status": belief.status.value}
452
+ for belief in result.beliefs
453
+ ],
454
+ "note": "Observed answers are illustrative, not pass/fail guarantees.",
455
+ }
456
+
457
+
458
+ def _settings_problems(environ: Mapping[str, str]) -> list[str]:
459
+ problems: list[str] = []
460
+ if not environ.get("OPENAI_API_KEY", "").strip():
461
+ problems.append("OPENAI_API_KEY is missing")
462
+ if not environ.get("DATABASE_URL", "").strip():
463
+ problems.append("DATABASE_URL is missing")
464
+ model = environ.get("MEM01_LLM_MODEL", "").strip() or LIVE_MODEL
465
+ if model != LIVE_MODEL:
466
+ problems.append(f"MEM01_LLM_MODEL must be {LIVE_MODEL}")
467
+ return problems
468
+
469
+
470
+ async def _run_live(environ: Mapping[str, str]) -> int:
471
+ problems = _settings_problems(environ)
472
+ if problems:
473
+ print(json.dumps({"ready": False, "problems": problems}))
474
+ return 2
475
+
476
+ invocation_id = uuid4().hex
477
+ user_id = f"build-week-live-{invocation_id}"
478
+ scenario = (
479
+ ("initial_location_and_rent", "I live in NYC and my monthly rent is $2,400."),
480
+ ("location_change", "I moved to SF this week."),
481
+ (
482
+ "current_location_and_prior_rent",
483
+ "Where do I live now, and what was my prior monthly rent?",
484
+ ),
485
+ ("unsupported_sister_name", "What is my sister's name?"),
486
+ )
487
+ sessions: list[Mem01Session] = []
488
+ observations: list[dict[str, Any]] = []
489
+ try:
490
+ agent = Agent(
491
+ name="Mem01Session live demo",
492
+ instructions=(
493
+ "Use only the current conversation and supplied memory. "
494
+ "If a personal fact is unsupported, say it is not stored."
495
+ ),
496
+ model=LIVE_MODEL,
497
+ )
498
+ for conversation, (label, prompt) in enumerate(scenario, start=1):
499
+ session = Mem01Session(
500
+ f"live-{invocation_id}-{conversation}",
501
+ user_id,
502
+ strict=True,
503
+ )
504
+ sessions.append(session)
505
+ config = session.run_config()
506
+ config.tracing_disabled = True
507
+ result = await Runner.run(
508
+ agent,
509
+ prompt,
510
+ session=cast(Any, session),
511
+ run_config=config,
512
+ )
513
+ observations.append(
514
+ {
515
+ "conversation": conversation,
516
+ "label": label,
517
+ "type": "model_observation",
518
+ "output": str(result.final_output),
519
+ }
520
+ )
521
+ beliefs = await sessions[-1].memory_history(
522
+ include_invalidated=True,
523
+ limit=100,
524
+ )
525
+ lifecycle = [
526
+ {
527
+ "content": belief.content,
528
+ "status": belief.status.value,
529
+ }
530
+ for belief in beliefs or []
531
+ ]
532
+ except Exception as error:
533
+ print(json.dumps({"live": "failed", "error_type": type(error).__name__}))
534
+ return 1
535
+ finally:
536
+ for session in sessions:
537
+ try:
538
+ await session.close()
539
+ except Exception:
540
+ pass
541
+
542
+ print(
543
+ json.dumps(
544
+ {
545
+ "mode": "live",
546
+ "model": LIVE_MODEL,
547
+ "conversation_count": len(scenario),
548
+ "observations": observations,
549
+ "lifecycle": lifecycle,
550
+ "note": "Outputs are observations, not deterministic guarantees.",
551
+ },
552
+ indent=2,
553
+ ensure_ascii=False,
554
+ )
555
+ )
556
+ return 0
557
+
558
+
559
+ async def _async_main(arguments: argparse.Namespace) -> int:
560
+ if arguments.check:
561
+ await asyncio.to_thread(mem01_env.load_env, override=False)
562
+ problems = _settings_problems(os.environ)
563
+ print(json.dumps({"ready": not problems, "problems": problems}))
564
+ return 2 if problems else 0
565
+ if arguments.live:
566
+ await asyncio.to_thread(mem01_env.load_env, override=False)
567
+ return await _run_live(os.environ)
568
+ result = await run_deterministic_demo()
569
+ if arguments.write_artifact is not None:
570
+ path = Path(arguments.write_artifact)
571
+ await asyncio.to_thread(
572
+ _write_text,
573
+ path,
574
+ render_artifact(generate_artifact_records(result)),
575
+ )
576
+ report = report_as_dict(result)
577
+ if arguments.json:
578
+ print(json.dumps(report, indent=2, ensure_ascii=False))
579
+ else:
580
+ print("Mem01Session deterministic Build Week evidence")
581
+ print(f"SDK runs: {report['sdk_runs']}; fixture: 40 conversations")
582
+ for record in report["measurements"]:
583
+ print(
584
+ f"{record['strategy']:>13} conversation {record['conversation']:>2}: "
585
+ f"{record['prepared_input_items']:>2} items, "
586
+ f"{record['estimated_tokens']:>4} estimated tokens"
587
+ )
588
+ print("Observed answers are illustrative, not pass/fail guarantees.")
589
+ return 0
590
+
591
+
592
+ def main(argv: Sequence[str] | None = None) -> int:
593
+ """CLI entry point; deterministic and key-free unless ``--live`` is set."""
594
+ parser = argparse.ArgumentParser(description=__doc__)
595
+ mode = parser.add_mutually_exclusive_group()
596
+ mode.add_argument("--live", action="store_true", help="Run one real API smoke test")
597
+ mode.add_argument(
598
+ "--check",
599
+ action="store_true",
600
+ help="Validate live settings without initializing clients or making calls",
601
+ )
602
+ parser.add_argument("--json", action="store_true", help="Print deterministic JSON")
603
+ parser.add_argument(
604
+ "--write-artifact",
605
+ nargs="?",
606
+ const=str(ARTIFACT_PATH),
607
+ metavar="PATH",
608
+ help="Regenerate the evidence artifact (default: repository artifact path)",
609
+ )
610
+ return asyncio.run(_async_main(parser.parse_args(argv)))
611
+
612
+
613
+ if __name__ == "__main__":
614
+ raise SystemExit(main())
@@ -0,0 +1,42 @@
1
+ [
2
+ {"conversation": 1, "user_input": "I live in NYC and my monthly rent is $2,400."},
3
+ {"conversation": 2, "user_input": "I moved to SF this week."},
4
+ {"conversation": 3, "user_input": "I prefer tea to coffee."},
5
+ {"conversation": 4, "user_input": "I usually work from home on Fridays."},
6
+ {"conversation": 5, "user_input": "I am planning a weekend hike."},
7
+ {"conversation": 6, "user_input": "I like concise project updates."},
8
+ {"conversation": 7, "user_input": "I am reading a book about urban design."},
9
+ {"conversation": 8, "user_input": "I need to buy groceries after work."},
10
+ {"conversation": 9, "user_input": "I prefer morning meetings."},
11
+ {"conversation": 10, "user_input": "Where do I live now, and what was my prior monthly rent?"},
12
+ {"conversation": 11, "user_input": "I enjoy walking to nearby parks."},
13
+ {"conversation": 12, "user_input": "I am learning to cook a new recipe."},
14
+ {"conversation": 13, "user_input": "I keep my notes in plain text."},
15
+ {"conversation": 14, "user_input": "I prefer quiet places for focused work."},
16
+ {"conversation": 15, "user_input": "I am reviewing my monthly budget."},
17
+ {"conversation": 16, "user_input": "I like science fiction films."},
18
+ {"conversation": 17, "user_input": "I plan tasks at the start of each week."},
19
+ {"conversation": 18, "user_input": "I enjoy trying neighborhood restaurants."},
20
+ {"conversation": 19, "user_input": "I prefer written follow-ups after meetings."},
21
+ {"conversation": 20, "user_input": "I am organizing old photographs."},
22
+ {"conversation": 21, "user_input": "I listen to instrumental music while working."},
23
+ {"conversation": 22, "user_input": "I am practicing a short presentation."},
24
+ {"conversation": 23, "user_input": "I like simple user interfaces."},
25
+ {"conversation": 24, "user_input": "I take a short walk at lunch."},
26
+ {"conversation": 25, "user_input": "I am comparing two open-source libraries."},
27
+ {"conversation": 26, "user_input": "I prefer reusable checklists."},
28
+ {"conversation": 27, "user_input": "I am cleaning up my downloads folder."},
29
+ {"conversation": 28, "user_input": "I like to test changes before sharing them."},
30
+ {"conversation": 29, "user_input": "I am planning meals for next week."},
31
+ {"conversation": 30, "user_input": "I prefer direct answers with supporting evidence."},
32
+ {"conversation": 31, "user_input": "I am setting aside time for documentation."},
33
+ {"conversation": 32, "user_input": "I enjoy visiting independent bookstores."},
34
+ {"conversation": 33, "user_input": "I am reviewing an accessibility checklist."},
35
+ {"conversation": 34, "user_input": "I prefer small, focused pull requests."},
36
+ {"conversation": 35, "user_input": "I am preparing for a design review."},
37
+ {"conversation": 36, "user_input": "I like keeping local development reproducible."},
38
+ {"conversation": 37, "user_input": "I am checking a release candidate."},
39
+ {"conversation": 38, "user_input": "I prefer examples that run without credentials."},
40
+ {"conversation": 39, "user_input": "Where do I live now, and what was my prior monthly rent?"},
41
+ {"conversation": 40, "user_input": "What is my sister's name?"}
42
+ ]