unique-orchestrator 2026.26.0.dev5__tar.gz → 2026.26.0.dev7__tar.gz

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.
Files changed (32) hide show
  1. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/PKG-INFO +4 -3
  2. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/pyproject.toml +9 -6
  3. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/config.py +14 -0
  4. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/prompts/system_prompt.jinja2 +10 -0
  5. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_config_services_validators.py +35 -0
  6. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/unique_ai.py +6 -1
  7. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/unique_ai_builder.py +27 -0
  8. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/README.md +0 -0
  9. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/__init__.py +0 -0
  10. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/_builders/__init__.py +0 -0
  11. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/_builders/inject_tool_reminders.py +0 -0
  12. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/_builders/loop_iteration_runner.py +0 -0
  13. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/_builders/open_file_setup.py +0 -0
  14. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/_builders/skill_setup.py +0 -0
  15. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/prompts/generic_reference_prompt.jinja2 +0 -0
  16. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/prompts/user_message_prompt.jinja2 +0 -0
  17. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/settings.py +0 -0
  18. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_agent_config_rjsf_ui_schema.py +0 -0
  19. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_build_loop_iteration_runner.py +0 -0
  20. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_qwen_max_loop_iterations.py +0 -0
  21. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_register_code_interpreter_postprocessors.py +0 -0
  22. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_async_modify.py +0 -0
  23. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_execution_timing.py +0 -0
  24. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_get_filtered_user_metadata.py +0 -0
  25. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_log_tool_calls.py +0 -0
  26. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_loop_debug_params.py +0 -0
  27. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_persist_tool_calls.py +0 -0
  28. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_plan_or_execute_include.py +0 -0
  29. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_reference_order.py +0 -0
  30. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_unique_ai_render_system_prompt_user_instructions.py +0 -0
  31. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/tests/test_utils_resolve_other_options.py +0 -0
  32. {unique_orchestrator-2026.26.0.dev5 → unique_orchestrator-2026.26.0.dev7}/unique_orchestrator/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-orchestrator
3
- Version: 2026.26.0.dev5
3
+ Version: 2026.26.0.dev7
4
4
  Summary:
5
5
  Author: Andreas Hauri
6
6
  Author-email: Andreas Hauri <andreas.hauri@unique.ai>
@@ -9,14 +9,15 @@ Requires-Dist: pydantic>=2.8.2,<3
9
9
  Requires-Dist: pydantic-settings>=2.10.1,<3
10
10
  Requires-Dist: typing-extensions>=4.9.0,<5
11
11
  Requires-Dist: jinja2>=3.1.0,<4
12
- Requires-Dist: unique-toolkit>=2026.26.0.dev7,<2026.26.0rc0
12
+ Requires-Dist: unique-toolkit>=2026.26.0.dev11,<2026.26.0rc0
13
13
  Requires-Dist: unique-stock-ticker>=2026.26.0.dev5,<2026.26.0rc0
14
14
  Requires-Dist: unique-follow-up-questions>=2026.26.0.dev5,<2026.26.0rc0
15
15
  Requires-Dist: unique-internal-search>=2026.26.0.dev5,<2026.26.0rc0
16
16
  Requires-Dist: unique-deep-research>=2026.26.0.dev5,<2026.26.0rc0
17
- Requires-Dist: unique-web-search>=2026.26.0.dev5,<2026.26.0rc0
17
+ Requires-Dist: unique-web-search>=2026.26.0.dev8,<2026.26.0rc0
18
18
  Requires-Dist: unique-swot>=2026.26.0.dev5,<2026.26.0rc0
19
19
  Requires-Dist: unique-skill-tool>=2026.26.0.dev5,<2026.26.0rc0
20
+ Requires-Dist: unique-user-memory>=2026.26.0.dev0,<2026.26.0rc0
20
21
  Requires-Dist: openai>=1.109.1,<3
21
22
  Requires-Python: >=3.12, <4
22
23
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_orchestrator"
3
- version = "2026.26.0.dev5"
3
+ version = "2026.26.0.dev7"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -13,14 +13,15 @@ dependencies = [
13
13
  "pydantic-settings>=2.10.1,<3",
14
14
  "typing-extensions>=4.9.0,<5",
15
15
  "jinja2>=3.1.0,<4",
16
- "unique-toolkit>=2026.26.0.dev7,<2026.26.0rc0",
16
+ "unique-toolkit>=2026.26.0.dev11,<2026.26.0rc0",
17
17
  "unique-stock-ticker>=2026.26.0.dev5,<2026.26.0rc0",
18
18
  "unique-follow-up-questions>=2026.26.0.dev5,<2026.26.0rc0",
19
19
  "unique-internal-search>=2026.26.0.dev5,<2026.26.0rc0",
20
20
  "unique-deep-research>=2026.26.0.dev5,<2026.26.0rc0",
21
- "unique-web-search>=2026.26.0.dev5,<2026.26.0rc0",
21
+ "unique-web-search>=2026.26.0.dev8,<2026.26.0rc0",
22
22
  "unique-swot>=2026.26.0.dev5,<2026.26.0rc0",
23
23
  "unique-skill-tool>=2026.26.0.dev5,<2026.26.0rc0",
24
+ "unique-user-memory>=2026.26.0.dev0,<2026.26.0rc0",
24
25
  "openai>=1.109.1,<3",
25
26
  ]
26
27
 
@@ -46,6 +47,7 @@ exclude-newer = "2 weeks"
46
47
  "unique-swot" = false
47
48
  "unique-toolkit" = false
48
49
  "unique-skill-tool" = false
50
+ "unique-user-memory" = false
49
51
  "unique-web-search" = false
50
52
 
51
53
  [tool.uv.sources]
@@ -56,6 +58,7 @@ unique-follow-up-questions = { workspace = true }
56
58
  unique-internal-search = { workspace = true }
57
59
  unique-swot = { workspace = true }
58
60
  unique-skill-tool = { workspace = true }
61
+ unique-user-memory = { workspace = true }
59
62
  unique-web-search = { workspace = true }
60
63
  unique-deep-research = { workspace = true }
61
64
 
@@ -86,12 +89,12 @@ filterwarnings = [
86
89
  ]
87
90
 
88
91
  [tool.deptry]
89
- known_first_party = ["unique_orchestrator", "unique_toolkit", "unique_stock_ticker", "unique_follow_up_questions", "unique_internal_search", "unique_deep_research", "unique_web_search", "unique_swot", "unique_skill_tool"]
92
+ known_first_party = ["unique_orchestrator", "unique_toolkit", "unique_stock_ticker", "unique_follow_up_questions", "unique_internal_search", "unique_deep_research", "unique_web_search", "unique_swot", "unique_skill_tool", "unique_user_memory"]
90
93
  extend_exclude = ["unique_orchestrator/tests"]
91
94
 
92
95
  [tool.deptry.per_rule_ignores]
93
- DEP002 = ["unique-toolkit", "unique-stock-ticker", "unique-follow-up-questions", "unique-internal-search", "unique-deep-research", "unique-web-search", "unique-swot", "unique-skill-tool"]
94
- DEP003 = ["unique_toolkit", "unique_stock_ticker", "unique_follow_up_questions", "unique_internal_search", "unique_deep_research", "unique_web_search", "unique_swot", "unique_skill_tool"]
96
+ DEP002 = ["unique-toolkit", "unique-stock-ticker", "unique-follow-up-questions", "unique-internal-search", "unique-deep-research", "unique-web-search", "unique-swot", "unique-skill-tool", "unique-user-memory"]
97
+ DEP003 = ["unique_toolkit", "unique_stock_ticker", "unique_follow_up_questions", "unique_internal_search", "unique_deep_research", "unique_web_search", "unique_swot", "unique_skill_tool", "unique_user_memory"]
95
98
 
96
99
  [tool.basedpyright]
97
100
  typeCheckingMode = "standard"
@@ -56,6 +56,7 @@ from unique_toolkit.agentic.tools.tool_progress_reporter import (
56
56
  )
57
57
  from unique_toolkit.language_model.default_language_model import DEFAULT_GPT_4o
58
58
  from unique_toolkit.language_model.infos import LanguageModelName, ModelCapabilities
59
+ from unique_user_memory.config import UserMemoryConfig
59
60
  from unique_web_search.config import WebSearchConfig
60
61
  from unique_web_search.service import WebSearchTool
61
62
 
@@ -110,6 +111,11 @@ class SpaceConfigBase(BaseToolConfig, Generic[T]):
110
111
  ),
111
112
  )
112
113
 
114
+ allow_user_memory: bool = Field(
115
+ default=False,
116
+ description=("Whether persistent per-user memory is active for this space."),
117
+ )
118
+
113
119
  switchable_language_models: list[SwitchableLanguageModelConfig] = Field(
114
120
  default_factory=list,
115
121
  description=("Language models selectable by chat users for a single message."),
@@ -286,6 +292,14 @@ class UniqueAIServices(BaseToolConfig):
286
292
  ToolProgressReporterConfig()
287
293
  )
288
294
 
295
+ user_memory_config: Annotated[
296
+ UserMemoryConfig, RJSFMetaTag.SpecialWidget.hidden()
297
+ ] = Field(
298
+ title="User Memory",
299
+ description="Configuration for persistent user memory.",
300
+ default_factory=UserMemoryConfig,
301
+ )
302
+
289
303
  @field_validator("stock_ticker_config", mode="before")
290
304
  @classmethod
291
305
  def check_if_stock_ticker_config_is_none(cls, stock_ticker_config):
@@ -25,6 +25,16 @@ Here is some metadata about the user, which may help you write better queries, a
25
25
  {%- endfor %}
26
26
  {%- endif %}
27
27
 
28
+ {#- User Memory Section #}
29
+ {% if user_memory and user_memory|length > 0 %}
30
+ # What I know about you
31
+ The following profile was distilled from past conversations with this user. Treat it as durable user context: adapt your tone, examples, and depth to it, but do not recite or quote it back verbatim, and do not treat it as authoritative for facts about the world.
32
+
33
+ You can read this memory while answering. Memory updates happen only in a postprocessing step after your final answer, so do not claim that you can directly edit the memory during the main response.
34
+
35
+ {{ user_memory }}
36
+ {%- endif %}
37
+
28
38
  Over the course of the conversation, you adapt to the user's tone and preference.
29
39
  Try to match the user's vibe, tone, and generally how they are speaking. You want the conversation to feel natural.
30
40
  You engage in authentic conversation by responding to the information provided, asking relevant questions, and showing genuine curiosity.
@@ -20,6 +20,7 @@ from unique_toolkit.language_model.infos import (
20
20
  ModelCapabilities,
21
21
  )
22
22
  from unique_toolkit.language_model.schemas import LanguageModelTokenLimits
23
+ from unique_user_memory.config import UserMemoryConfig
23
24
 
24
25
  from unique_orchestrator.config import (
25
26
  EvaluationConfig,
@@ -463,3 +464,37 @@ class TestUniqueAIConfigInjectTodoToolValidator:
463
464
 
464
465
  assert self._todo_entries(config) == []
465
466
  assert [t.name for t in config.space.tools] == [t.name for t in default_tools]
467
+
468
+
469
+ class TestUniqueAIConfigUserMemory:
470
+ def test_user_memory_config_defaults_to_disabled(self):
471
+ config = UniqueAIConfig()
472
+
473
+ assert isinstance(config.agent.services.user_memory_config, UserMemoryConfig)
474
+ assert config.agent.services.user_memory_config.enabled is False
475
+
476
+ def test_user_memory_config_parses_enabled_payload(self):
477
+ config = UniqueAIConfig(
478
+ agent={
479
+ "services": {
480
+ "user_memory_config": {
481
+ "enabled": True,
482
+ "max_tokens": 1500,
483
+ }
484
+ }
485
+ }
486
+ )
487
+
488
+ memory_config = config.agent.services.user_memory_config
489
+ assert memory_config.enabled is True
490
+ assert memory_config.max_tokens == 1500
491
+
492
+ def test_allow_user_memory_defaults_to_false(self):
493
+ config = UniqueAIConfig()
494
+
495
+ assert config.space.allow_user_memory is False
496
+
497
+ def test_allow_user_memory_parses_camel_case_payload(self):
498
+ config = UniqueAIConfig(space={"allowUserMemory": True})
499
+
500
+ assert config.space.allow_user_memory is True
@@ -85,6 +85,7 @@ class UniqueAI:
85
85
  loop_iteration_runner: LoopIterationRunner,
86
86
  agent_file_registry: list[str] | None = None,
87
87
  uploaded_documents: list[Content] | None = None,
88
+ user_memory_text: str = "",
88
89
  ) -> None: ...
89
90
 
90
91
  # Responses API Dependencies
@@ -109,6 +110,7 @@ class UniqueAI:
109
110
  loop_iteration_runner: ResponsesLoopIterationRunner,
110
111
  agent_file_registry: list[str] | None = None,
111
112
  uploaded_documents: list[Content] | None = None,
113
+ user_memory_text: str = "",
112
114
  ) -> None: ...
113
115
 
114
116
  def __init__(
@@ -132,6 +134,7 @@ class UniqueAI:
132
134
  loop_iteration_runner: LoopIterationRunner | ResponsesLoopIterationRunner,
133
135
  agent_file_registry: list[str] | None = None,
134
136
  uploaded_documents: list[Content] | None = None,
137
+ user_memory_text: str = "",
135
138
  ) -> None:
136
139
  self._logger = logger
137
140
  self._event = event
@@ -139,6 +142,7 @@ class UniqueAI:
139
142
  self._chat_service = chat_service
140
143
  self._content_service = content_service
141
144
  self._uploaded_documents = uploaded_documents or []
145
+ self._user_memory_text = user_memory_text
142
146
  self._skill_choices = getattr(event.payload, "skill_choices", [])
143
147
 
144
148
  self._debug_info_manager = debug_info_manager
@@ -553,6 +557,7 @@ class UniqueAI:
553
557
  sub_agent_referencing_instructions=sub_agent_referencing_instructions,
554
558
  user_metadata=user_metadata,
555
559
  uploaded_documents_expired=uploaded_documents_expired,
560
+ user_memory=self._user_memory_text,
556
561
  )
557
562
  return system_message
558
563
 
@@ -623,7 +628,7 @@ class UniqueAI:
623
628
  result.is_positive for result in evaluation_results.unpack()
624
629
  ):
625
630
  self._logger.warning(
626
- "we should add here the retry counter add an instruction and retry the loop for now we just exit the loop"
631
+ "we should add here the retry counter add an instruction and retry the loop for now we just exit the loop."
627
632
  ) # TODO: add retry counter and instruction
628
633
 
629
634
  return True
@@ -70,6 +70,8 @@ from unique_toolkit.content import Content
70
70
  from unique_toolkit.content.service import ContentService
71
71
  from unique_toolkit.language_model.infos import LanguageModelInfo, ModelCapabilities
72
72
  from unique_toolkit.protocols.support import ResponsesSupportCompleteWithReferences
73
+ from unique_user_memory.user_memory import load_user_memory
74
+ from unique_user_memory.user_memory_postprocessor import UserMemoryPostprocessor
73
75
 
74
76
  from unique_orchestrator._builders import (
75
77
  build_loop_iteration_runner,
@@ -177,6 +179,7 @@ class _CommonComponents(NamedTuple):
177
179
  mcp_manager: MCPManager
178
180
  a2a_manager: A2AManager
179
181
  mcp_servers: list[McpServer]
182
+ user_memory_text: str
180
183
 
181
184
 
182
185
  def _apply_model_choice_override(
@@ -356,6 +359,27 @@ async def _build_common(
356
359
  )
357
360
  )
358
361
 
362
+ user_memory_text = ""
363
+ if (
364
+ config.space.allow_user_memory
365
+ or config.agent.services.user_memory_config.enabled
366
+ ):
367
+ user_memory_state = await load_user_memory(
368
+ event=event,
369
+ config=config.agent.services.user_memory_config,
370
+ logger=logger,
371
+ )
372
+ if user_memory_state is not None:
373
+ user_memory_text = user_memory_state.text
374
+ postprocessor_manager.add_postprocessor(
375
+ UserMemoryPostprocessor(
376
+ config=config.agent.services.user_memory_config,
377
+ event=event,
378
+ state=user_memory_state,
379
+ logger=logger,
380
+ )
381
+ )
382
+
359
383
  return _CommonComponents(
360
384
  chat_service=chat_service,
361
385
  content_service=content_service,
@@ -371,6 +395,7 @@ async def _build_common(
371
395
  a2a_manager=a2a_manager,
372
396
  tool_manager_config=tool_manager_config,
373
397
  mcp_servers=event.payload.mcp_servers,
398
+ user_memory_text=user_memory_text,
374
399
  postprocessor_manager=postprocessor_manager,
375
400
  response_watcher=response_watcher,
376
401
  message_step_logger=MessageStepLogger(chat_service),
@@ -570,6 +595,7 @@ async def _build_responses(
570
595
  mcp_servers=event.payload.mcp_servers,
571
596
  loop_iteration_runner=loop_iteration_runner,
572
597
  agent_file_registry=agent_file_registry,
598
+ user_memory_text=common_components.user_memory_text,
573
599
  )
574
600
 
575
601
 
@@ -649,6 +675,7 @@ async def _build_completions(
649
675
  mcp_servers=event.payload.mcp_servers,
650
676
  message_step_logger=common_components.message_step_logger,
651
677
  loop_iteration_runner=loop_iteration_runner,
678
+ user_memory_text=common_components.user_memory_text,
652
679
  )
653
680
 
654
681