zrb 1.21.17__py3-none-any.whl → 1.21.33__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.
Files changed (41) hide show
  1. zrb/attr/type.py +10 -7
  2. zrb/builtin/git.py +12 -1
  3. zrb/builtin/llm/chat_completion.py +287 -0
  4. zrb/builtin/llm/chat_session_cmd.py +90 -28
  5. zrb/builtin/llm/chat_trigger.py +6 -1
  6. zrb/builtin/llm/tool/cli.py +29 -13
  7. zrb/builtin/llm/tool/code.py +9 -1
  8. zrb/builtin/llm/tool/file.py +32 -6
  9. zrb/builtin/llm/tool/note.py +9 -9
  10. zrb/builtin/llm/tool/search/__init__.py +1 -0
  11. zrb/builtin/llm/tool/search/brave.py +66 -0
  12. zrb/builtin/llm/tool/search/searxng.py +61 -0
  13. zrb/builtin/llm/tool/search/serpapi.py +61 -0
  14. zrb/builtin/llm/tool/sub_agent.py +30 -10
  15. zrb/builtin/llm/tool/web.py +17 -72
  16. zrb/config/config.py +67 -26
  17. zrb/config/default_prompt/interactive_system_prompt.md +16 -13
  18. zrb/config/default_prompt/summarization_prompt.md +54 -8
  19. zrb/config/default_prompt/system_prompt.md +16 -18
  20. zrb/config/llm_rate_limitter.py +15 -6
  21. zrb/input/option_input.py +13 -1
  22. zrb/task/llm/agent.py +42 -143
  23. zrb/task/llm/agent_runner.py +152 -0
  24. zrb/task/llm/conversation_history.py +35 -24
  25. zrb/task/llm/conversation_history_model.py +4 -11
  26. zrb/task/llm/history_processor.py +206 -0
  27. zrb/task/llm/history_summarization.py +2 -179
  28. zrb/task/llm/print_node.py +14 -5
  29. zrb/task/llm/prompt.py +2 -17
  30. zrb/task/llm/subagent_conversation_history.py +41 -0
  31. zrb/task/llm/tool_confirmation_completer.py +41 -0
  32. zrb/task/llm/tool_wrapper.py +15 -11
  33. zrb/task/llm_task.py +41 -40
  34. zrb/util/attr.py +12 -7
  35. zrb/util/git.py +2 -2
  36. zrb/xcom/xcom.py +10 -0
  37. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/METADATA +3 -3
  38. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/RECORD +40 -32
  39. zrb/task/llm/history_summarization_tool.py +0 -24
  40. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/WHEEL +0 -0
  41. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/entry_points.txt +0 -0
zrb/task/llm/agent.py CHANGED
@@ -1,22 +1,16 @@
1
1
  import inspect
2
- import json
3
2
  from collections.abc import Callable
4
3
  from dataclasses import dataclass
5
4
  from typing import TYPE_CHECKING, Any
6
5
 
7
- from zrb.config.llm_rate_limitter import LLMRateLimiter, llm_rate_limitter
6
+ from zrb.config.llm_rate_limitter import LLMRateLimitter
8
7
  from zrb.context.any_context import AnyContext
9
- from zrb.context.any_shared_context import AnySharedContext
10
- from zrb.task.llm.error import extract_api_error_details
11
- from zrb.task.llm.print_node import print_node
8
+ from zrb.task.llm.history_processor import create_summarize_history_processor
12
9
  from zrb.task.llm.tool_wrapper import wrap_func, wrap_tool
13
- from zrb.task.llm.typing import ListOfDict
14
- from zrb.util.cli.style import stylize_faint
15
10
 
16
11
  if TYPE_CHECKING:
17
12
  from pydantic_ai import Agent, Tool
18
- from pydantic_ai.agent import AgentRun
19
- from pydantic_ai.messages import UserContent
13
+ from pydantic_ai._agent_graph import HistoryProcessor
20
14
  from pydantic_ai.models import Model
21
15
  from pydantic_ai.output import OutputDataT, OutputSpec
22
16
  from pydantic_ai.settings import ModelSettings
@@ -28,13 +22,21 @@ if TYPE_CHECKING:
28
22
  def create_agent_instance(
29
23
  ctx: AnyContext,
30
24
  model: "str | Model",
25
+ rate_limitter: LLMRateLimitter | None = None,
31
26
  output_type: "OutputSpec[OutputDataT]" = str,
32
27
  system_prompt: str = "",
33
28
  model_settings: "ModelSettings | None" = None,
34
- tools: "list[ToolOrCallable]" = [],
29
+ tools: list["ToolOrCallable"] = [],
35
30
  toolsets: list["AbstractToolset[None]"] = [],
36
31
  retries: int = 3,
37
32
  yolo_mode: bool | list[str] | None = None,
33
+ summarization_model: "Model | str | None" = None,
34
+ summarization_model_settings: "ModelSettings | None" = None,
35
+ summarization_system_prompt: str | None = None,
36
+ summarization_retries: int = 2,
37
+ summarization_token_threshold: int | None = None,
38
+ history_processors: list["HistoryProcessor"] | None = None,
39
+ auto_summarize: bool = True,
38
40
  ) -> "Agent[None, Any]":
39
41
  """Creates a new Agent instance with configured tools and servers."""
40
42
  from pydantic_ai import Agent, RunContext, Tool
@@ -102,6 +104,21 @@ def create_agent_instance(
102
104
  ConfirmationWrapperToolset(wrapped=toolset, ctx=ctx, yolo_mode=yolo_mode)
103
105
  for toolset in toolsets
104
106
  ]
107
+ # Create History processor with summarizer
108
+ history_processors = [] if history_processors is None else history_processors
109
+ if auto_summarize:
110
+ history_processors += [
111
+ create_summarize_history_processor(
112
+ ctx=ctx,
113
+ system_prompt=system_prompt,
114
+ rate_limitter=rate_limitter,
115
+ summarization_model=summarization_model,
116
+ summarization_model_settings=summarization_model_settings,
117
+ summarization_system_prompt=summarization_system_prompt,
118
+ summarization_token_threshold=summarization_token_threshold,
119
+ summarization_retries=summarization_retries,
120
+ )
121
+ ]
105
122
  # Return Agent
106
123
  return Agent[None, Any](
107
124
  model=model,
@@ -111,12 +128,14 @@ def create_agent_instance(
111
128
  toolsets=wrapped_toolsets,
112
129
  model_settings=model_settings,
113
130
  retries=retries,
131
+ history_processors=history_processors,
114
132
  )
115
133
 
116
134
 
117
135
  def get_agent(
118
136
  ctx: AnyContext,
119
137
  model: "str | Model",
138
+ rate_limitter: LLMRateLimitter | None = None,
120
139
  output_type: "OutputSpec[OutputDataT]" = str,
121
140
  system_prompt: str = "",
122
141
  model_settings: "ModelSettings | None" = None,
@@ -128,6 +147,12 @@ def get_agent(
128
147
  additional_toolsets: "list[AbstractToolset[None] | str]" = [],
129
148
  retries: int = 3,
130
149
  yolo_mode: bool | list[str] | None = None,
150
+ summarization_model: "Model | str | None" = None,
151
+ summarization_model_settings: "ModelSettings | None" = None,
152
+ summarization_system_prompt: str | None = None,
153
+ summarization_retries: int = 2,
154
+ summarization_token_threshold: int | None = None,
155
+ history_processors: list["HistoryProcessor"] | None = None,
131
156
  ) -> "Agent":
132
157
  """Retrieves the configured Agent instance or creates one if necessary."""
133
158
  # Get tools for agent
@@ -143,6 +168,7 @@ def get_agent(
143
168
  return create_agent_instance(
144
169
  ctx=ctx,
145
170
  model=model,
171
+ rate_limitter=rate_limitter,
146
172
  output_type=output_type,
147
173
  system_prompt=system_prompt,
148
174
  tools=tools,
@@ -150,6 +176,12 @@ def get_agent(
150
176
  model_settings=model_settings,
151
177
  retries=retries,
152
178
  yolo_mode=yolo_mode,
179
+ summarization_model=summarization_model,
180
+ summarization_model_settings=summarization_model_settings,
181
+ summarization_system_prompt=summarization_system_prompt,
182
+ summarization_retries=summarization_retries,
183
+ summarization_token_threshold=summarization_token_threshold,
184
+ history_processors=history_processors,
153
185
  )
154
186
 
155
187
 
@@ -170,136 +202,3 @@ def _render_toolset_or_str_list(
170
202
  continue
171
203
  toolsets.append(toolset_or_str)
172
204
  return toolsets
173
-
174
-
175
- async def run_agent_iteration(
176
- ctx: AnyContext,
177
- agent: "Agent[None, Any]",
178
- user_prompt: str,
179
- attachments: "list[UserContent] | None" = None,
180
- history_list: ListOfDict | None = None,
181
- rate_limitter: LLMRateLimiter | None = None,
182
- max_retry: int = 2,
183
- log_indent_level: int = 0,
184
- ) -> "AgentRun":
185
- """
186
- Runs a single iteration of the agent execution loop.
187
-
188
- Args:
189
- ctx: The task context.
190
- agent: The Pydantic AI agent instance.
191
- user_prompt: The user's input prompt.
192
- history_list: The current conversation history.
193
-
194
- Returns:
195
- The agent run result object.
196
-
197
- Raises:
198
- Exception: If any error occurs during agent execution.
199
- """
200
- if max_retry < 0:
201
- raise ValueError("Max retry cannot be less than 0")
202
- attempt = 0
203
- while attempt < max_retry:
204
- try:
205
- return await _run_single_agent_iteration(
206
- ctx=ctx,
207
- agent=agent,
208
- user_prompt=user_prompt,
209
- attachments=[] if attachments is None else attachments,
210
- history_list=[] if history_list is None else history_list,
211
- rate_limitter=(
212
- llm_rate_limitter if rate_limitter is None else rate_limitter
213
- ),
214
- log_indent_level=log_indent_level,
215
- )
216
- except BaseException:
217
- attempt += 1
218
- if attempt == max_retry:
219
- raise
220
- raise Exception("Max retry exceeded")
221
-
222
-
223
- async def _run_single_agent_iteration(
224
- ctx: AnyContext,
225
- agent: "Agent",
226
- user_prompt: str,
227
- attachments: "list[UserContent]",
228
- history_list: ListOfDict,
229
- rate_limitter: LLMRateLimiter,
230
- log_indent_level: int,
231
- ) -> "AgentRun":
232
- from openai import APIError
233
- from pydantic_ai.messages import ModelMessagesTypeAdapter
234
-
235
- agent_payload = _estimate_request_payload(
236
- agent, user_prompt, attachments, history_list
237
- )
238
- callback = _create_print_throttle_notif(ctx)
239
- if rate_limitter:
240
- await rate_limitter.throttle(agent_payload, callback)
241
- else:
242
- await llm_rate_limitter.throttle(agent_payload, callback)
243
- user_prompt_with_attachments = [user_prompt] + attachments
244
- async with agent:
245
- async with agent.iter(
246
- user_prompt=user_prompt_with_attachments,
247
- message_history=ModelMessagesTypeAdapter.validate_python(history_list),
248
- ) as agent_run:
249
- async for node in agent_run:
250
- # Each node represents a step in the agent's execution
251
- try:
252
- await print_node(
253
- _get_plain_printer(ctx), agent_run, node, log_indent_level
254
- )
255
- except APIError as e:
256
- # Extract detailed error information from the response
257
- error_details = extract_api_error_details(e)
258
- ctx.log_error(f"API Error: {error_details}")
259
- raise
260
- except Exception as e:
261
- ctx.log_error(f"Error processing node: {str(e)}")
262
- ctx.log_error(f"Error type: {type(e).__name__}")
263
- raise
264
- return agent_run
265
-
266
-
267
- def _create_print_throttle_notif(ctx: AnyContext) -> Callable[[], None]:
268
- def _print_throttle_notif():
269
- ctx.print(stylize_faint(" ⌛>> Request Throttled"), plain=True)
270
-
271
- return _print_throttle_notif
272
-
273
-
274
- def _estimate_request_payload(
275
- agent: "Agent",
276
- user_prompt: str,
277
- attachments: "list[UserContent]",
278
- history_list: ListOfDict,
279
- ) -> str:
280
- system_prompts = agent._system_prompts if hasattr(agent, "_system_prompts") else ()
281
- return json.dumps(
282
- [
283
- {"role": "system", "content": "\n".join(system_prompts)},
284
- *history_list,
285
- {"role": "user", "content": user_prompt},
286
- *[_estimate_attachment_payload(attachment) for attachment in attachments],
287
- ]
288
- )
289
-
290
-
291
- def _estimate_attachment_payload(attachment: "UserContent") -> Any:
292
- if hasattr(attachment, "url"):
293
- return {"role": "user", "content": attachment.url}
294
- if hasattr(attachment, "data"):
295
- return {"role": "user", "content": "x" * len(attachment.data)}
296
- return ""
297
-
298
-
299
- def _get_plain_printer(ctx: AnyContext):
300
- def printer(*args, **kwargs):
301
- if "plain" not in kwargs:
302
- kwargs["plain"] = True
303
- return ctx.print(*args, **kwargs)
304
-
305
- return printer
@@ -0,0 +1,152 @@
1
+ import json
2
+ from collections.abc import Callable
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from zrb.config.llm_rate_limitter import LLMRateLimitter, llm_rate_limitter
6
+ from zrb.context.any_context import AnyContext
7
+ from zrb.task.llm.error import extract_api_error_details
8
+ from zrb.task.llm.print_node import print_node
9
+ from zrb.task.llm.typing import ListOfDict
10
+ from zrb.util.cli.style import stylize_faint
11
+
12
+ if TYPE_CHECKING:
13
+ from pydantic_ai import Agent, Tool
14
+ from pydantic_ai.agent import AgentRun
15
+ from pydantic_ai.messages import UserContent
16
+
17
+ ToolOrCallable = Tool | Callable
18
+
19
+
20
+ async def run_agent_iteration(
21
+ ctx: AnyContext,
22
+ agent: "Agent[None, Any]",
23
+ user_prompt: str,
24
+ attachments: "list[UserContent] | None" = None,
25
+ history_list: ListOfDict | None = None,
26
+ rate_limitter: LLMRateLimitter | None = None,
27
+ max_retry: int = 2,
28
+ log_indent_level: int = 0,
29
+ ) -> "AgentRun":
30
+ """
31
+ Runs a single iteration of the agent execution loop.
32
+
33
+ Args:
34
+ ctx: The task context.
35
+ agent: The Pydantic AI agent instance.
36
+ user_prompt: The user's input prompt.
37
+ history_list: The current conversation history.
38
+
39
+ Returns:
40
+ The agent run result object.
41
+
42
+ Raises:
43
+ Exception: If any error occurs during agent execution.
44
+ """
45
+ if max_retry < 0:
46
+ raise ValueError("Max retry cannot be less than 0")
47
+ attempt = 0
48
+ while attempt < max_retry:
49
+ try:
50
+ return await _run_single_agent_iteration(
51
+ ctx=ctx,
52
+ agent=agent,
53
+ user_prompt=user_prompt,
54
+ attachments=[] if attachments is None else attachments,
55
+ history_list=[] if history_list is None else history_list,
56
+ rate_limitter=(
57
+ llm_rate_limitter if rate_limitter is None else rate_limitter
58
+ ),
59
+ log_indent_level=log_indent_level,
60
+ )
61
+ except BaseException:
62
+ attempt += 1
63
+ if attempt == max_retry:
64
+ raise
65
+ raise Exception("Max retry exceeded")
66
+
67
+
68
+ async def _run_single_agent_iteration(
69
+ ctx: AnyContext,
70
+ agent: "Agent",
71
+ user_prompt: str,
72
+ attachments: "list[UserContent]",
73
+ history_list: ListOfDict,
74
+ rate_limitter: LLMRateLimitter,
75
+ log_indent_level: int,
76
+ ) -> "AgentRun":
77
+ from openai import APIError
78
+ from pydantic_ai import UsageLimits
79
+ from pydantic_ai.messages import ModelMessagesTypeAdapter
80
+
81
+ agent_payload = _estimate_request_payload(
82
+ agent, user_prompt, attachments, history_list
83
+ )
84
+ callback = _create_print_throttle_notif(ctx)
85
+ if rate_limitter:
86
+ await rate_limitter.throttle(agent_payload, callback)
87
+ else:
88
+ await llm_rate_limitter.throttle(agent_payload, callback)
89
+ user_prompt_with_attachments = [user_prompt] + attachments
90
+ async with agent:
91
+ async with agent.iter(
92
+ user_prompt=user_prompt_with_attachments,
93
+ message_history=ModelMessagesTypeAdapter.validate_python(history_list),
94
+ usage_limits=UsageLimits(request_limit=None), # We don't want limit
95
+ ) as agent_run:
96
+ async for node in agent_run:
97
+ # Each node represents a step in the agent's execution
98
+ try:
99
+ await print_node(
100
+ _get_plain_printer(ctx), agent_run, node, log_indent_level
101
+ )
102
+ except APIError as e:
103
+ # Extract detailed error information from the response
104
+ error_details = extract_api_error_details(e)
105
+ ctx.log_error(f"API Error: {error_details}")
106
+ raise
107
+ except Exception as e:
108
+ ctx.log_error(f"Error processing node: {str(e)}")
109
+ ctx.log_error(f"Error type: {type(e).__name__}")
110
+ raise
111
+ return agent_run
112
+
113
+
114
+ def _create_print_throttle_notif(ctx: AnyContext) -> Callable[[str], None]:
115
+ def _print_throttle_notif(reason: str):
116
+ ctx.print(stylize_faint(f" ⌛>> Request Throttled: {reason}"), plain=True)
117
+
118
+ return _print_throttle_notif
119
+
120
+
121
+ def _estimate_request_payload(
122
+ agent: "Agent",
123
+ user_prompt: str,
124
+ attachments: "list[UserContent]",
125
+ history_list: ListOfDict,
126
+ ) -> str:
127
+ system_prompts = agent._system_prompts if hasattr(agent, "_system_prompts") else ()
128
+ return json.dumps(
129
+ [
130
+ {"role": "system", "content": "\n".join(system_prompts)},
131
+ *history_list,
132
+ {"role": "user", "content": user_prompt},
133
+ *[_estimate_attachment_payload(attachment) for attachment in attachments],
134
+ ]
135
+ )
136
+
137
+
138
+ def _estimate_attachment_payload(attachment: "UserContent") -> Any:
139
+ if hasattr(attachment, "url"):
140
+ return {"role": "user", "content": attachment.url}
141
+ if hasattr(attachment, "data"):
142
+ return {"role": "user", "content": "x" * len(attachment.data)}
143
+ return ""
144
+
145
+
146
+ def _get_plain_printer(ctx: AnyContext):
147
+ def printer(*args, **kwargs):
148
+ if "plain" not in kwargs:
149
+ kwargs["plain"] = True
150
+ return ctx.print(*args, **kwargs)
151
+
152
+ return printer
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import os
3
3
  from collections.abc import Callable
4
- from copy import deepcopy
5
4
  from typing import Any
6
5
 
7
6
  from zrb.attr.type import StrAttr
@@ -13,6 +12,37 @@ from zrb.util.attr import get_str_attr
13
12
  from zrb.util.file import read_file, write_file
14
13
  from zrb.util.markdown import make_markdown_section
15
14
  from zrb.util.run import run_async
15
+ from zrb.xcom.xcom import Xcom
16
+
17
+
18
+ def _get_global_subagent_messages_xcom(ctx: AnyContext) -> Xcom:
19
+ if "_global_subagents" not in ctx.xcom:
20
+ ctx.xcom["_global_subagents"] = Xcom([{}])
21
+ if not isinstance(ctx.xcom["_global_subagents"], Xcom):
22
+ raise ValueError("ctx.xcom._global_subagents must be an Xcom")
23
+ return ctx.xcom["_global_subagents"]
24
+
25
+
26
+ def inject_subagent_history_into_ctx(
27
+ ctx: AnyContext, conversation_history: ConversationHistory
28
+ ):
29
+ subagent_messages_xcom = _get_global_subagent_messages_xcom(ctx)
30
+ existing_subagent_history = subagent_messages_xcom.get({})
31
+ subagent_messages_xcom.set(
32
+ {**existing_subagent_history, **conversation_history.subagent_history}
33
+ )
34
+
35
+
36
+ def set_ctx_subagent_history(ctx: AnyContext, subagent_name: str, messages: ListOfDict):
37
+ subagent_messages_xcom = _get_global_subagent_messages_xcom(ctx)
38
+ subagent_history = subagent_messages_xcom.get({})
39
+ subagent_history[subagent_name] = messages
40
+ subagent_messages_xcom.set(subagent_history)
41
+
42
+
43
+ def get_subagent_histories_from_ctx(ctx: AnyContext) -> dict[str, ListOfDict]:
44
+ subagent_messsages_xcom = _get_global_subagent_messages_xcom(ctx)
45
+ return subagent_messsages_xcom.get({})
16
46
 
17
47
 
18
48
  def inject_conversation_history_notes(conversation_history: ConversationHistory):
@@ -56,7 +86,10 @@ def get_history_file(
56
86
 
57
87
  async def _read_from_source(
58
88
  ctx: AnyContext,
59
- reader: Callable[[AnyContext], dict[str, Any] | list | None] | None,
89
+ reader: (
90
+ Callable[[AnyContext], ConversationHistory | dict[str, Any] | list | None]
91
+ | None
92
+ ),
60
93
  file_path: str | None,
61
94
  ) -> "ConversationHistory | None":
62
95
  # Priority 1: Reader function
@@ -165,28 +198,6 @@ async def write_conversation_history(
165
198
  write_file(history_file, json.dumps(history_data.to_dict(), indent=2))
166
199
 
167
200
 
168
- def replace_system_prompt_in_history(
169
- history_list: ListOfDict, replacement: str = "<main LLM system prompt>"
170
- ) -> ListOfDict:
171
- """
172
- Returns a new history list where any part with part_kind 'system-prompt'
173
- has its 'content' replaced with the given replacement string.
174
- Args:
175
- history: List of history items (each item is a dict with a 'parts' list).
176
- replacement: The string to use in place of system-prompt content.
177
-
178
- Returns:
179
- A deep-copied list of history items with system-prompt content replaced.
180
- """
181
- new_history = deepcopy(history_list)
182
- for item in new_history:
183
- parts = item.get("parts", [])
184
- for part in parts:
185
- if part.get("part_kind") == "system-prompt":
186
- part["content"] = replacement
187
- return new_history
188
-
189
-
190
201
  def count_part_in_history_list(history_list: ListOfDict) -> int:
191
202
  """Calculates the total number of 'parts' in a history list."""
192
203
  history_part_len = 0
@@ -10,27 +10,24 @@ class ConversationHistory:
10
10
 
11
11
  def __init__(
12
12
  self,
13
- past_conversation_summary: str = "",
14
- past_conversation_transcript: str = "",
15
13
  history: ListOfDict | None = None,
16
14
  contextual_note: str | None = None,
17
15
  long_term_note: str | None = None,
18
16
  project_path: str | None = None,
17
+ subagent_history: dict[str, ListOfDict] | None = None,
19
18
  ):
20
- self.past_conversation_transcript = past_conversation_transcript
21
- self.past_conversation_summary = past_conversation_summary
22
19
  self.history = history if history is not None else []
23
20
  self.contextual_note = contextual_note if contextual_note is not None else ""
24
21
  self.long_term_note = long_term_note if long_term_note is not None else ""
25
22
  self.project_path = project_path if project_path is not None else os.getcwd()
23
+ self.subagent_history = subagent_history if subagent_history is not None else {}
26
24
 
27
25
  def to_dict(self) -> dict[str, Any]:
28
26
  return {
29
- "past_conversation_summary": self.past_conversation_summary,
30
- "past_conversation_transcript": self.past_conversation_transcript,
31
27
  "history": self.history,
32
28
  "contextual_note": self.contextual_note,
33
29
  "long_term_note": self.long_term_note,
30
+ "subagent_history": self.subagent_history,
34
31
  }
35
32
 
36
33
  def model_dump_json(self, indent: int = 2) -> str:
@@ -44,15 +41,11 @@ class ConversationHistory:
44
41
  if isinstance(data, cls):
45
42
  return data # Already a valid instance
46
43
  if isinstance(data, dict):
47
- # This handles both the new format and the old {'context': ..., 'history': ...}
48
44
  return cls(
49
- past_conversation_summary=data.get("past_conversation_summary", ""),
50
- past_conversation_transcript=data.get(
51
- "past_conversation_transcript", ""
52
- ),
53
45
  history=data.get("history", data.get("messages", [])),
54
46
  contextual_note=data.get("contextual_note", ""),
55
47
  long_term_note=data.get("long_term_note", ""),
48
+ subagent_history=data.get("subagent_history", {}),
56
49
  )
57
50
  elif isinstance(data, list):
58
51
  # Handle very old format (just a list) - wrap it