langchain-agentx-python 2.0.1__py3-none-any.whl → 2.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.
@@ -11,7 +11,7 @@ from langchain_agentx import create_loop_agent
11
11
  ```
12
12
  """
13
13
 
14
- __version__ = "2.0.1"
14
+ __version__ = "2.1.0"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -70,7 +70,6 @@ from ..exit.reason_codes import (
70
70
  TRANSITION_MAX_OUTPUT_TOKENS_ESCALATE,
71
71
  TRANSITION_MAX_OUTPUT_TOKENS_RECOVERY,
72
72
  TRANSITION_MAX_OUTPUT_TOKENS_RECOVERY_EXHAUSTED,
73
- TRANSITION_REACTIVE_COMPACT_RETRY,
74
73
  TRANSITION_STOP_HOOK_BLOCKING,
75
74
  TRANSITION_STOP_HOOK_PREVENTED,
76
75
  TRANSITION_TASK_NOTIFICATION_ARRIVED,
@@ -452,6 +451,8 @@ class LoopControllerEdge:
452
451
  step=state.get("step", 0),
453
452
  turn_count=int(state.get("turn_count", 0) or 0),
454
453
  provider_finish_reason=state.get("finish_reason"),
454
+ error_kind="context_window",
455
+ recovery_exhausted=True,
455
456
  )
456
457
  state["transition"] = {
457
458
  "reason": TRANSITION_CONTEXT_WINDOW_RECOVERY_EXHAUSTED,
@@ -585,23 +586,6 @@ class LoopControllerEdge:
585
586
  state,
586
587
  destination=self._model_destination,
587
588
  )
588
- if not state.get("has_attempted_reactive_compact", False):
589
- state["has_attempted_reactive_compact"] = True
590
- state["transition"] = {
591
- "reason": TRANSITION_REACTIVE_COMPACT_RETRY,
592
- "error_reason": withheld_error.get("reason"),
593
- }
594
- emit_loop_decision_event(
595
- state=state,
596
- decision="continue",
597
- selected_edge=self._model_destination,
598
- reason_code="reactive_compact_retry",
599
- evidence={"withheld_error": withheld_error, "step": state.get("step", 0)},
600
- )
601
- return self._set_continue_resolution(
602
- state,
603
- destination=self._model_destination,
604
- )
605
589
  state["transition"] = {
606
590
  "reason": TRANSITION_WITHHOLD_RECOVERY_RETRY,
607
591
  "error_reason": withheld_error.get("reason"),
@@ -171,6 +171,7 @@ class ReactiveCompactNode:
171
171
  terminal = build_loop_terminal(
172
172
  reason=TERMINAL_REASON_MAX_TOKENS,
173
173
  error_kind="context_window",
174
+ recovery_exhausted=True,
174
175
  )
175
176
  patch = {
176
177
  STATE_HAS_ATTEMPTED_REACTIVE_COMPACT: True,
@@ -1248,6 +1248,29 @@ class ModelNode(Runnable):
1248
1248
  and max_output_tokens_override is not None
1249
1249
  and max_output_tokens_recovery_count < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT
1250
1250
  )
1251
+ if (
1252
+ should_end
1253
+ and terminal_reason in RECOVERABLE_TERMINAL_REASONS
1254
+ and overflow_kind == "context_window"
1255
+ ):
1256
+ withheld_error = build_withheld_error(
1257
+ terminal_reason=terminal_reason,
1258
+ finish_reason=finish_reason,
1259
+ last_ai_message=last_ai_message,
1260
+ )
1261
+ return {
1262
+ "should_end": False,
1263
+ "terminal_reason": None,
1264
+ "withheld_error": withheld_error,
1265
+ "withhold_retry_count": withhold_retry_count,
1266
+ "max_output_tokens_override": max_output_tokens_override,
1267
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
1268
+ "transition": {
1269
+ "reason": TRANSITION_WITHHOLD_RECOVERY_RETRY,
1270
+ "error_reason": withheld_error["reason"],
1271
+ "attempt": withhold_retry_count,
1272
+ },
1273
+ }
1251
1274
  if should_end and terminal_reason in RECOVERABLE_TERMINAL_REASONS and (
1252
1275
  can_default_withhold_retry or can_max_tokens_recovery_retry
1253
1276
  ):
@@ -10,6 +10,7 @@ loop_driver.py — Loop-aware shared stream driver.
10
10
  当前裁剪范围:
11
11
  root closeout 捕获、synthetic error/finish、orphan tool_result repair(stream surface)。
12
12
  正常结束但缺 root on_chain_end(LangGraph) 时打 anomaly WARNING(不伪造 synthetic finish)。
13
+ R1 反应恢复:单次 drain 内 budgeted continue 重入(S2,对齐 CC queryLoop continue)。
13
14
  """
14
15
 
15
16
  from __future__ import annotations
@@ -23,6 +24,7 @@ from langgraph.errors import GraphRecursionError
23
24
  from ..exit.reason_codes import (
24
25
  TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
25
26
  TERMINAL_REASON_RUNTIME_BUDGET_EXHAUSTED,
27
+ TRANSITION_REACTIVE_COMPACT_RETRY,
26
28
  )
27
29
  from ..exit.terminal import apply_terminal_reason_projection, build_loop_terminal, read_terminal_reason
28
30
  from langchain_agentx.observability.events.tool_call_id_resolver import ToolCallIdResolver
@@ -57,6 +59,7 @@ class LoopStreamDriver:
57
59
  *,
58
60
  config: dict[str, Any] | None = None,
59
61
  version: str = "v2",
62
+ max_reactive_continue_per_drain: int = 0,
60
63
  ) -> AsyncIterator[dict[str, Any]]:
61
64
  self._graph_input = dict(graph_input)
62
65
  self._assistant_turn_count = 0
@@ -66,83 +69,173 @@ class LoopStreamDriver:
66
69
  self.result.last_raw_event = None
67
70
  self.result.saw_chat_model_end_last_turn = False
68
71
  self.result.saw_partial_superseded = False
72
+ self.result.reactive_continue_drains = 0
73
+ current_input = dict(graph_input)
74
+ reactive_continues = 0
69
75
  try:
70
- try:
71
- async for event in graph.astream_events(
72
- graph_input,
76
+ while True:
77
+ async for event in self._stream_single_drain(
78
+ graph,
79
+ current_input,
73
80
  config=config,
74
81
  version=version,
75
82
  ):
76
- self._observe_event(event)
77
83
  yield event
78
- self.result.graph_completed = True
79
- self._log_missing_root_chain_end_anomaly()
80
- except asyncio.CancelledError as exc:
81
- self.result.final_error = exc
82
- for repair in self._orphan_tool_result_repairs(closeout_kind="cancelled"):
83
- yield repair
84
- raise
85
- except GraphRecursionError as exc:
86
- self.result.final_error = exc
87
- self._log_catch_error(exc)
88
- for repair in self._orphan_tool_result_repairs(closeout_kind="interrupted"):
89
- yield repair
90
- if not self.result.saw_error_visible_surface:
91
- self.result.saw_error_visible_surface = True
92
- yield build_synthetic_error_visible_event(
93
- message="Runtime budget exhausted (recursion limit reached)",
94
- error_kind="graph_recursion_error",
95
- )
96
- if not self.result.saw_finish_surface:
97
- terminal = build_loop_terminal(
98
- reason=TERMINAL_REASON_RUNTIME_BUDGET_EXHAUSTED,
99
- error_kind="graph_recursion_error",
100
- step=(self.result.last_loop_snapshot or {}).get("step"),
101
- turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
102
- provider_finish_reason=(self.result.last_loop_snapshot or {}).get("finish_reason"),
103
- errors=[str(exc)],
104
- metadata={
105
- "langgraph_error_type": "GraphRecursionError",
106
- },
107
- )
108
- self.result.terminal = terminal
109
- synthetic_output = self._build_synthetic_graph_output(terminal)
110
- self.result.graph_output = synthetic_output
111
- self.result.saw_finish_surface = True
112
- yield build_synthetic_root_chain_end(graph_output=synthetic_output)
113
- self._log_catch_error_diagnostic(exc)
114
- raise
115
- except Exception as exc:
116
- self.result.final_error = exc
117
- # CC catch 协议 §5.3.3:先记录日志 / 观测,再补 stream surface,最后 re-raise
118
- self._log_catch_error(exc)
119
- for repair in self._orphan_tool_result_repairs(closeout_kind="interrupted"):
120
- yield repair
121
- if not self.result.saw_error_visible_surface:
122
- self.result.saw_error_visible_surface = True
123
- yield build_synthetic_error_visible_event(
124
- message=str(exc),
125
- error_kind="graph_runtime_error",
126
- )
127
- if not self.result.saw_finish_surface:
128
- terminal = build_loop_terminal(
129
- reason=TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
130
- error_kind="graph_runtime_error",
131
- step=(self.result.last_loop_snapshot or {}).get("step"),
132
- turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
133
- provider_finish_reason=(self.result.last_loop_snapshot or {}).get("finish_reason"),
134
- errors=[str(exc)],
135
- )
136
- self.result.terminal = terminal
137
- synthetic_output = self._build_synthetic_graph_output(terminal)
138
- self.result.graph_output = synthetic_output
139
- self.result.saw_finish_surface = True
140
- yield build_synthetic_root_chain_end(graph_output=synthetic_output)
141
- self._log_catch_error_diagnostic(exc)
142
- raise
84
+ if not self._should_reactive_continue_drain(
85
+ continues_used=reactive_continues,
86
+ max_continues=max_reactive_continue_per_drain,
87
+ ):
88
+ break
89
+ reactive_continues += 1
90
+ self.result.reactive_continue_drains = reactive_continues
91
+ current_input = self._build_continue_graph_input(
92
+ current_input,
93
+ self.result.graph_output or {},
94
+ )
95
+ self._graph_input = dict(current_input)
96
+ self._reset_for_continue_drain()
97
+ logger.info(
98
+ "LoopStreamDriver R1 continue: drain %s/%s transition=%s",
99
+ reactive_continues,
100
+ max_reactive_continue_per_drain,
101
+ (self.result.graph_output or {}).get("transition", {}).get("reason"),
102
+ )
143
103
  finally:
144
104
  self._log_stream_closeout_summary()
145
105
 
106
+ async def _stream_single_drain(
107
+ self,
108
+ graph: Any,
109
+ graph_input: dict[str, Any],
110
+ *,
111
+ config: dict[str, Any] | None = None,
112
+ version: str = "v2",
113
+ ) -> AsyncIterator[dict[str, Any]]:
114
+ try:
115
+ async for event in graph.astream_events(
116
+ graph_input,
117
+ config=config,
118
+ version=version,
119
+ ):
120
+ self._observe_event(event)
121
+ yield event
122
+ self.result.graph_completed = True
123
+ self._log_missing_root_chain_end_anomaly()
124
+ except asyncio.CancelledError as exc:
125
+ self.result.final_error = exc
126
+ for repair in self._orphan_tool_result_repairs(closeout_kind="cancelled"):
127
+ yield repair
128
+ raise
129
+ except GraphRecursionError as exc:
130
+ self.result.final_error = exc
131
+ self._log_catch_error(exc)
132
+ for repair in self._orphan_tool_result_repairs(closeout_kind="interrupted"):
133
+ yield repair
134
+ if not self.result.saw_error_visible_surface:
135
+ self.result.saw_error_visible_surface = True
136
+ yield build_synthetic_error_visible_event(
137
+ message="Runtime budget exhausted (recursion limit reached)",
138
+ error_kind="graph_recursion_error",
139
+ )
140
+ if not self.result.saw_finish_surface:
141
+ terminal = build_loop_terminal(
142
+ reason=TERMINAL_REASON_RUNTIME_BUDGET_EXHAUSTED,
143
+ error_kind="graph_recursion_error",
144
+ step=(self.result.last_loop_snapshot or {}).get("step"),
145
+ turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
146
+ provider_finish_reason=(self.result.last_loop_snapshot or {}).get("finish_reason"),
147
+ errors=[str(exc)],
148
+ metadata={
149
+ "langgraph_error_type": "GraphRecursionError",
150
+ },
151
+ )
152
+ self.result.terminal = terminal
153
+ synthetic_output = self._build_synthetic_graph_output(terminal)
154
+ self.result.graph_output = synthetic_output
155
+ self.result.saw_finish_surface = True
156
+ yield build_synthetic_root_chain_end(graph_output=synthetic_output)
157
+ self._log_catch_error_diagnostic(exc)
158
+ raise
159
+ except Exception as exc:
160
+ self.result.final_error = exc
161
+ # CC catch 协议 §5.3.3:先记录日志 / 观测,再补 stream surface,最后 re-raise
162
+ self._log_catch_error(exc)
163
+ for repair in self._orphan_tool_result_repairs(closeout_kind="interrupted"):
164
+ yield repair
165
+ if not self.result.saw_error_visible_surface:
166
+ self.result.saw_error_visible_surface = True
167
+ yield build_synthetic_error_visible_event(
168
+ message=str(exc),
169
+ error_kind="graph_runtime_error",
170
+ )
171
+ if not self.result.saw_finish_surface:
172
+ terminal = build_loop_terminal(
173
+ reason=TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
174
+ error_kind="graph_runtime_error",
175
+ step=(self.result.last_loop_snapshot or {}).get("step"),
176
+ turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
177
+ provider_finish_reason=(self.result.last_loop_snapshot or {}).get("finish_reason"),
178
+ errors=[str(exc)],
179
+ )
180
+ self.result.terminal = terminal
181
+ synthetic_output = self._build_synthetic_graph_output(terminal)
182
+ self.result.graph_output = synthetic_output
183
+ self.result.saw_finish_surface = True
184
+ yield build_synthetic_root_chain_end(graph_output=synthetic_output)
185
+ self._log_catch_error_diagnostic(exc)
186
+ raise
187
+
188
+ def _should_reactive_continue_drain(
189
+ self,
190
+ *,
191
+ continues_used: int,
192
+ max_continues: int,
193
+ ) -> bool:
194
+ if max_continues <= 0 or continues_used >= max_continues:
195
+ return False
196
+ if self.result.final_error is not None:
197
+ return False
198
+ if not self.result.saw_root_chain_end or not self.result.graph_completed:
199
+ return False
200
+ output = self.result.graph_output
201
+ if not isinstance(output, dict):
202
+ return False
203
+ resolution = output.get("resolution")
204
+ if not isinstance(resolution, dict) or resolution.get("kind") != "continue":
205
+ return False
206
+ transition = resolution.get("transition")
207
+ if not isinstance(transition, dict):
208
+ transition = output.get("transition")
209
+ if not isinstance(transition, dict):
210
+ return False
211
+ return transition.get("reason") == TRANSITION_REACTIVE_COMPACT_RETRY
212
+
213
+ def _build_continue_graph_input(
214
+ self,
215
+ prior_input: dict[str, Any],
216
+ graph_output: dict[str, Any],
217
+ ) -> dict[str, Any]:
218
+ continued = dict(prior_input)
219
+ messages = graph_output.get("messages")
220
+ if isinstance(messages, list):
221
+ continued["messages"] = list(messages)
222
+ for key in (
223
+ "has_attempted_reactive_compact",
224
+ "_session_id",
225
+ "_run_id",
226
+ "_trace_collector",
227
+ ):
228
+ if key in graph_output:
229
+ continued[key] = graph_output[key]
230
+ return continued
231
+
232
+ def _reset_for_continue_drain(self) -> None:
233
+ self._pending_tool_calls.clear()
234
+ self.result.saw_root_chain_end = False
235
+ self.result.saw_finish_surface = False
236
+ self.result.graph_completed = False
237
+ self.result.final_error = None
238
+
146
239
  def _observe_event(self, event: dict[str, Any]) -> None:
147
240
  event_name = str(event.get("event") or "")
148
241
  name = str(event.get("name") or "")
@@ -31,6 +31,7 @@ class StreamDriverResult:
31
31
  last_raw_event: str | None = None
32
32
  saw_chat_model_end_last_turn: bool = False
33
33
  saw_partial_superseded: bool = False
34
+ reactive_continue_drains: int = 0
34
35
 
35
36
 
36
37
  __all__ = ["StreamDriverResult"]
@@ -29,6 +29,12 @@ from collections.abc import Callable
29
29
  from dataclasses import dataclass, field
30
30
  from typing import Any, Literal
31
31
 
32
+ from langchain_agentx.observability.events.finish_outcome import (
33
+ RecoveryPolicy,
34
+ RecoveryPolicyMode,
35
+ coerce_recovery_policy,
36
+ )
37
+
32
38
  from langchain_agentx.loop.context.query_source import LoopCompilationContext
33
39
  from langchain_agentx.loop.exit.query_source import QuerySource
34
40
  from langchain_agentx.loop.runtime.subagent_execution_paths import (
@@ -107,6 +113,9 @@ class SubagentContext:
107
113
  # Thinking 控制字段(request-side 生成意图,与 UI 展示意图分离)
108
114
  thinking_generation_enabled: bool = True # 子代理 request-side thinking 生成开关
109
115
 
116
+ # R1 恢复策略(S4→S2;None 在 runner 层按 interactive 解析)
117
+ recovery: RecoveryPolicy | None = None
118
+
110
119
 
111
120
  @dataclass
112
121
  class SubagentResult:
@@ -150,6 +159,7 @@ def create_subagent_context(
150
159
  thinking_generation_enabled: bool = True,
151
160
  query_source: str | None = None,
152
161
  is_builtin_subagent: bool = False,
162
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
153
163
  ) -> SubagentContext:
154
164
  """工厂函数:从父上下文创建子 Agent 上下文
155
165
 
@@ -285,6 +295,14 @@ def create_subagent_context(
285
295
  effective_cwd=effective_cwd,
286
296
  )
287
297
 
298
+ resolved_recovery: RecoveryPolicy | None = None
299
+ if recovery is not None:
300
+ resolved_recovery = coerce_recovery_policy(recovery)
301
+ else:
302
+ parent_recovery = getattr(parent_ctx, "recovery", None)
303
+ if isinstance(parent_recovery, (RecoveryPolicy, str)):
304
+ resolved_recovery = coerce_recovery_policy(parent_recovery)
305
+
288
306
  return SubagentContext(
289
307
  agent_id=agent_id,
290
308
  initial_messages=initial_messages,
@@ -317,6 +335,7 @@ def create_subagent_context(
317
335
  available_tools=list(available_tools or []),
318
336
  thinking_generation_enabled=thinking_generation_enabled,
319
337
  query_source=resolved_query_source,
338
+ recovery=resolved_recovery,
320
339
  )
321
340
 
322
341
 
@@ -55,6 +55,11 @@ from langchain_agentx.loop.message_contract import (
55
55
  is_synthetic_transcript_tool_id,
56
56
  to_record,
57
57
  )
58
+ from langchain_agentx.observability.events.finish_outcome import (
59
+ RecoveryPolicy,
60
+ RecoveryPolicyMode,
61
+ coerce_recovery_policy,
62
+ )
58
63
  from langchain_agentx.observability.events.tool_call_id_resolver import ToolCallIdResolver
59
64
  from langchain_agentx.observability.events.tool_outcome_resolver import (
60
65
  error_message_for_outcome,
@@ -504,6 +509,7 @@ async def _collect_subagent_events(
504
509
  run_tags: list[str],
505
510
  transcript: SubagentTranscriptManager,
506
511
  trace: SubagentExecutionTrace,
512
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
507
513
  ) -> _EventCollectionResult:
508
514
  collected_assistant_messages: list[Any] = []
509
515
  terminal_reason = "completed"
@@ -533,12 +539,16 @@ async def _collect_subagent_events(
533
539
  config["configurable"] = dict(configurable)
534
540
 
535
541
  try:
542
+ recovery_policy = coerce_recovery_policy(
543
+ recovery if recovery is not None else ctx.recovery
544
+ )
536
545
  driver = LoopStreamDriver()
537
546
  async for event in driver.run(
538
547
  graph,
539
548
  graph_input,
540
549
  config=config,
541
550
  version="v2",
551
+ max_reactive_continue_per_drain=recovery_policy.reactive_continue_budget(),
542
552
  ):
543
553
  event_name = event.get("event", "")
544
554
  data = event.get("data") or {}
@@ -667,6 +677,7 @@ async def run_subagent(
667
677
  loader: Any,
668
678
  model: Any | None = None,
669
679
  allowed_tool_names: set[str] | None = None,
680
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
670
681
  ) -> SubagentResult:
671
682
  """运行子 Agent(plain/fork 同步路径)。
672
683
 
@@ -715,6 +726,7 @@ async def run_subagent(
715
726
  run_tags=run_tags,
716
727
  transcript=transcript,
717
728
  trace=trace,
729
+ recovery=recovery,
718
730
  )
719
731
  return await _build_subagent_result(
720
732
  ctx=ctx,
@@ -12,7 +12,9 @@ from .events import (
12
12
  LangGraphToLangchainAgentEventAdapter,
13
13
  RecoveryAction,
14
14
  RecoveryActionResolver,
15
+ RecoveryPolicy,
15
16
  RecoveryPolicyMode,
17
+ coerce_recovery_policy,
16
18
  create_langgraph_event_adapter,
17
19
  enrich_run_result,
18
20
  )
@@ -69,7 +71,9 @@ __all__ = [
69
71
  "create_langgraph_event_adapter",
70
72
  "RecoveryAction",
71
73
  "RecoveryActionResolver",
74
+ "RecoveryPolicy",
72
75
  "RecoveryPolicyMode",
76
+ "coerce_recovery_policy",
73
77
  "enrich_run_result",
74
78
  "REASON_CODES",
75
79
  "export_call_chain",
@@ -13,7 +13,9 @@ from .finish_outcome import (
13
13
  FinishOutcomeParser,
14
14
  RecoveryAction,
15
15
  RecoveryActionResolver,
16
+ RecoveryPolicy,
16
17
  RecoveryPolicyMode,
18
+ coerce_recovery_policy,
17
19
  enrich_run_result,
18
20
  )
19
21
  from .factory import (
@@ -32,7 +34,9 @@ __all__ = [
32
34
  "FinishOutcomeParser",
33
35
  "RecoveryAction",
34
36
  "RecoveryActionResolver",
37
+ "RecoveryPolicy",
35
38
  "RecoveryPolicyMode",
39
+ "coerce_recovery_policy",
36
40
  "enrich_run_result",
37
41
  "ILangGraphEventAdapter",
38
42
  "create_langgraph_event_adapter",
@@ -54,6 +54,34 @@ FINISH_OUTCOME_KEYS = frozenset(
54
54
  RecoveryPolicyMode = Literal["interactive", "auto"]
55
55
 
56
56
 
57
+ @dataclass(frozen=True)
58
+ class RecoveryPolicy:
59
+ """Stream 恢复策略配置面(S4 消费方 → S2 LoopStreamDriver 注入)。
60
+
61
+ 非 Session 编排 API:仅控制 R1 reactive continue 预算与 finish 消费模式。
62
+ """
63
+
64
+ mode: RecoveryPolicyMode = "interactive"
65
+ max_reactive_continue_per_drain: int = 1
66
+
67
+ def reactive_continue_budget(self) -> int:
68
+ """返回 LoopStreamDriver R1 continue 预算(interactive 禁用自动 continue)。"""
69
+ if self.mode == "interactive":
70
+ return 0
71
+ return self.max_reactive_continue_per_drain
72
+
73
+
74
+ def coerce_recovery_policy(
75
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
76
+ ) -> RecoveryPolicy:
77
+ """将消费方传入的 recovery 规范化为 RecoveryPolicy。"""
78
+ if recovery is None:
79
+ return RecoveryPolicy()
80
+ if isinstance(recovery, str):
81
+ return RecoveryPolicy(mode=recovery)
82
+ return recovery
83
+
84
+
57
85
  class RecoveryAction(str, Enum):
58
86
  """消费方建议动作(不含具体 UI 文案)。"""
59
87
 
@@ -248,6 +276,8 @@ __all__ = [
248
276
  "FinishOutcomeParser",
249
277
  "RecoveryAction",
250
278
  "RecoveryActionResolver",
279
+ "RecoveryPolicy",
251
280
  "RecoveryPolicyMode",
281
+ "coerce_recovery_policy",
252
282
  "enrich_run_result",
253
283
  ]
@@ -20,10 +20,8 @@ from .plan_mode import (
20
20
  )
21
21
  from .protocol import LoopContainer
22
22
  from .recovery import (
23
- SessionRecoveryRunResult,
24
23
  finish_outcome_from_graph_output,
25
- parse_result_finish_outcome,
26
- run_with_recovery_loop,
24
+ finish_outcome_from_stream_driver_result,
27
25
  )
28
26
 
29
27
  __all__ = [
@@ -35,15 +33,13 @@ __all__ = [
35
33
  "DEFAULT_CLEANUP_ACTION_TIMEOUT",
36
34
  "ConversationSession",
37
35
  "LoopContainer",
38
- "SessionRecoveryRunResult",
39
36
  "create_agent_session",
40
37
  "create_conversation_session",
41
38
  "build_plan_mode_transition_event",
42
39
  "enter_plan_mode",
43
40
  "exit_plan_mode",
44
41
  "finish_outcome_from_graph_output",
42
+ "finish_outcome_from_stream_driver_result",
45
43
  "load_session_messages",
46
- "parse_result_finish_outcome",
47
44
  "PlanModeTransition",
48
- "run_with_recovery_loop",
49
45
  ]
@@ -38,13 +38,16 @@ from ..command.builtin import (
38
38
  make_memory_command,
39
39
  make_reload_plugins_command,
40
40
  )
41
- from ..observability.events.finish_outcome import RecoveryPolicyMode, enrich_run_result
41
+ from ..observability.events.finish_outcome import (
42
+ RecoveryPolicy,
43
+ RecoveryPolicyMode,
44
+ coerce_recovery_policy,
45
+ enrich_run_result,
46
+ )
42
47
  from ..loop.config import merge_run_config
43
48
  from .recovery import (
44
- SessionRecoveryRunResult,
45
49
  finish_outcome_from_graph_output,
46
50
  finish_outcome_from_stream_driver_result,
47
- run_with_recovery_loop,
48
51
  )
49
52
  from ..loop.config.loop_runtime_overlay import LoopRuntimeOverlay
50
53
  from ..loop.context.compaction_service import default_loop_context_compaction
@@ -226,37 +229,6 @@ class AgentSession:
226
229
  finally:
227
230
  reset_command_tool_scope(store)
228
231
 
229
- async def resume_after_compact(self) -> dict[str, Any]:
230
- """compact 后重跑当前轮:不再 append 新的 HumanMessage。
231
-
232
- 适用于 CLI `/compact` 或 headless auto 恢复的第二跳;与 ``run_loop(same_query)``
233
- 不同,不会把同一用户问题重复写入 messages / transcript。
234
- """
235
- return await self._run_loop_turn(user_input=None)
236
-
237
- async def run_with_recovery(
238
- self,
239
- user_input: str,
240
- *,
241
- mode: RecoveryPolicyMode = "auto",
242
- max_outer_retries: int = 1,
243
- progress_callback: Any = None,
244
- ) -> SessionRecoveryRunResult:
245
- """B1 外层自动恢复:recoverable 时 compact + resume,受 ``max_outer_retries`` 约束。
246
-
247
- - ``mode="auto"``:可恢复且未 exhausted 时自动 compact 并 resume。
248
- - ``mode="interactive"``:只跑一轮,把 ``RecoveryAction`` 留给 CLI 决策。
249
- """
250
- return await run_with_recovery_loop(
251
- user_input=user_input,
252
- mode=mode,
253
- max_outer_retries=max_outer_retries,
254
- run_new_turn=lambda inp: self._run_loop_turn(user_input=inp),
255
- resume_current_turn=lambda: self._run_loop_turn(user_input=None),
256
- compact=self.compact_context,
257
- progress_callback=progress_callback,
258
- )
259
-
260
232
  async def _run_loop_turn(self, *, user_input: str | None) -> dict[str, Any]:
261
233
  if self._graph is None:
262
234
  raise RuntimeError("AgentSession graph is not initialized")
@@ -302,6 +274,7 @@ class AgentSession:
302
274
  user_input: str,
303
275
  *,
304
276
  config: dict[str, Any] | None = None,
277
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
305
278
  ) -> AsyncIterator[dict[str, Any]]:
306
279
  """
307
280
  流式执行一轮 loop,复用 AgentSession 的消息上下文。
@@ -322,6 +295,7 @@ class AgentSession:
322
295
  output_messages: list[Any] | None = None
323
296
  graph_output: dict[str, Any] | None = None
324
297
  run_config = merge_run_config(self._graph, config)
298
+ recovery_policy = coerce_recovery_policy(recovery)
325
299
  driver = LoopStreamDriver()
326
300
  try:
327
301
  try:
@@ -333,6 +307,7 @@ class AgentSession:
333
307
  },
334
308
  config=run_config,
335
309
  version="v2",
310
+ max_reactive_continue_per_drain=recovery_policy.reactive_continue_budget(),
336
311
  ):
337
312
  if event.get("event") == "on_chain_end" and event.get("name") == "LangGraph":
338
313
  data = event.get("data", {}) or {}
@@ -594,4 +569,4 @@ class AgentSession:
594
569
  self._runtime_command_registry.register(make_reload_plugins_command())
595
570
 
596
571
 
597
- __all__ = ["AgentSession", "SessionRecoveryRunResult"]
572
+ __all__ = ["AgentSession"]
@@ -40,6 +40,11 @@ from ..loop.context.compaction_service import default_loop_context_compaction
40
40
  from ..loop.context.message_utils import total_estimated_tokens_for_messages
41
41
  from ..loop.hook.types import HookContext, HookEvent
42
42
  from ..loop.stream.loop_driver import LoopStreamDriver
43
+ from ..observability.events.finish_outcome import (
44
+ RecoveryPolicy,
45
+ RecoveryPolicyMode,
46
+ coerce_recovery_policy,
47
+ )
43
48
  from ..memory.memdir.extractor import drain_pending_extraction
44
49
  from ..plugin.loader import PluginLoader
45
50
  from ..plugin.registries import AgentRegistry, CommandRegistry as PluginCommandRegistry, SkillRegistry
@@ -264,6 +269,7 @@ class ConversationSession:
264
269
  user_input: str,
265
270
  *,
266
271
  config: dict[str, Any] | None = None,
272
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
267
273
  ):
268
274
  """流式执行单轮对话,graph 每次新建。"""
269
275
  from ..session.recovery import (
@@ -282,6 +288,7 @@ class ConversationSession:
282
288
  output_messages: list[Any] | None = None
283
289
  graph_output: dict[str, Any] | None = None
284
290
  run_config = merge_run_config(graph, config)
291
+ recovery_policy = coerce_recovery_policy(recovery)
285
292
  driver = LoopStreamDriver()
286
293
  try:
287
294
  async for event in driver.run(
@@ -292,6 +299,7 @@ class ConversationSession:
292
299
  },
293
300
  config=run_config,
294
301
  version="v2",
302
+ max_reactive_continue_per_drain=recovery_policy.reactive_continue_budget(),
295
303
  ):
296
304
  if event.get("event") == "on_chain_end" and event.get("name") == "LangGraph":
297
305
  data = event.get("data", {}) or {}
@@ -1,55 +1,21 @@
1
1
  """
2
- session/recovery.py — AgentSession 外层恢复编排(B1 auto / interactive 分流)
2
+ session/recovery.py — Stream closeout helpers for AgentSession.
3
3
 
4
4
  职责:
5
- run_loop + compact_context 之上封装「compact 后继续同一问题」与 auto 模式重试预算,
6
- 避免消费方重复 append 同一条 HumanMessage。
5
+ graph output / LoopStreamDriver.result 构建 finish_outcome 字典。
7
6
 
8
7
  链路位置:
9
- AgentSession.run_with_recovery / resume_after_compact → 本模块
8
+ AgentSession.stream_loop_events closeout → 本模块
10
9
 
11
10
  当前裁剪范围:
12
- - 不做图内 reactive_compact 拓扑修复(见 release 文档缺口 2)
13
- - 不做 B2 透明自恢复(单次 invoke 内无感知恢复)
11
+ stream closeout helper;R1 反应恢复归 LoopStreamDriver(见 error-flow 设计 2026-07-11)。
14
12
  """
15
13
 
16
14
  from __future__ import annotations
17
15
 
18
- from dataclasses import dataclass
19
- from typing import Any, Awaitable, Callable, Protocol
16
+ from typing import Any
20
17
 
21
- from langchain_agentx.observability.events.finish_outcome import (
22
- FinishOutcome,
23
- FinishOutcomeParser,
24
- RecoveryAction,
25
- RecoveryActionResolver,
26
- RecoveryPolicyMode,
27
- )
28
-
29
-
30
- class _CompactCallable(Protocol):
31
- async def __call__(self, *, progress_callback: Any = None) -> tuple[int, int]: ...
32
-
33
-
34
- class _RunOnceCallable(Protocol):
35
- async def __call__(self) -> dict[str, Any]: ...
36
-
37
-
38
- @dataclass(frozen=True)
39
- class SessionRecoveryRunResult:
40
- """run_with_recovery 返回:最后一轮 graph 结果 + 终态语义 + 外层 compact 次数。"""
41
-
42
- result: dict[str, Any]
43
- outcome: FinishOutcome
44
- action: RecoveryAction
45
- outer_recovery_attempts: int
46
-
47
-
48
- def parse_result_finish_outcome(result: dict[str, Any]) -> FinishOutcome:
49
- raw = result.get("finish_outcome")
50
- if isinstance(raw, dict):
51
- return FinishOutcomeParser().parse_finish_data(raw)
52
- return FinishOutcomeParser().from_loop_state(result)
18
+ from langchain_agentx.observability.events.finish_outcome import FinishOutcomeParser
53
19
 
54
20
 
55
21
  def finish_outcome_from_graph_output(output: dict[str, Any] | None) -> dict[str, Any]:
@@ -78,53 +44,7 @@ def finish_outcome_from_stream_driver_result(result: Any) -> dict[str, Any] | No
78
44
  return finish_outcome_from_graph_output(output)
79
45
 
80
46
 
81
- async def run_with_recovery_loop(
82
- *,
83
- user_input: str,
84
- mode: RecoveryPolicyMode,
85
- max_outer_retries: int,
86
- run_new_turn: Callable[[str], Awaitable[dict[str, Any]]],
87
- resume_current_turn: Callable[[], Awaitable[dict[str, Any]]],
88
- compact: _CompactCallable,
89
- progress_callback: Any = None,
90
- ) -> SessionRecoveryRunResult:
91
- """B1 外层编排:首轮 run_new_turn;recoverable 时 compact + resume(不重复 append)。"""
92
- if max_outer_retries < 0:
93
- raise ValueError("max_outer_retries must be >= 0")
94
-
95
- result = await run_new_turn(user_input)
96
- outcome = parse_result_finish_outcome(result)
97
- action = RecoveryActionResolver.resolve(outcome, mode=mode)
98
- attempts = 0
99
-
100
- while attempts < max_outer_retries:
101
- if outcome.succeeded:
102
- break
103
- if RecoveryActionResolver.blocks_auto_compact(outcome):
104
- break
105
- if mode == "interactive":
106
- break
107
- resolved = RecoveryActionResolver.resolve(outcome, mode="auto")
108
- if resolved != RecoveryAction.AUTO_COMPACT:
109
- break
110
- await compact(progress_callback=progress_callback)
111
- attempts += 1
112
- result = await resume_current_turn()
113
- outcome = parse_result_finish_outcome(result)
114
- action = RecoveryActionResolver.resolve(outcome, mode=mode)
115
-
116
- return SessionRecoveryRunResult(
117
- result=result,
118
- outcome=outcome,
119
- action=action,
120
- outer_recovery_attempts=attempts,
121
- )
122
-
123
-
124
47
  __all__ = [
125
- "SessionRecoveryRunResult",
126
48
  "finish_outcome_from_graph_output",
127
49
  "finish_outcome_from_stream_driver_result",
128
- "parse_result_finish_outcome",
129
- "run_with_recovery_loop",
130
50
  ]
@@ -33,6 +33,10 @@ from langchain_agentx.tool_runtime.models import (
33
33
  )
34
34
  from langchain_agentx.tool_runtime.state_bridge import ToolStateBridge
35
35
  from langchain_agentx.tools.grep.backend import GrepExecutionError, RipgrepBackend
36
+ from langchain_agentx.tools.search_path_resolver import (
37
+ policy_read_roots_from_engine,
38
+ resolve_search_root_for_prefixed_pattern,
39
+ )
36
40
  from langchain_agentx.tools.ripgrep_plugin_exclusions import PluginCacheGlobExclusions
37
41
  from langchain_agentx.utils.path import expand_path
38
42
  from langchain_agentx.utils.path_user_input import UserPathInputNormalizer
@@ -129,12 +133,19 @@ class GlobRuntimeTool(RuntimeTool):
129
133
  data["pattern"] = pattern.strip()
130
134
 
131
135
  path = data.get("path")
136
+ default_root = resolve_tool_cwd(ctx)
137
+ pattern = str(data.get("pattern") or "").strip()
132
138
  if isinstance(path, str) and path.strip():
133
139
  data["path"] = self._path_normalizer.normalize_and_expand(
134
- path, base_dir=resolve_tool_cwd(ctx)
140
+ path, base_dir=default_root
135
141
  )
136
142
  else:
137
- data["path"] = resolve_tool_cwd(ctx)
143
+ data["path"] = resolve_search_root_for_prefixed_pattern(
144
+ pattern,
145
+ default_root,
146
+ ctx,
147
+ policy_read_roots=policy_read_roots_from_engine(self._policy),
148
+ )
138
149
  return data
139
150
 
140
151
  def validate_input(
@@ -27,6 +27,10 @@ from langchain_agentx.tool_runtime.models import (
27
27
  )
28
28
 
29
29
  from langchain_agentx.tools.ripgrep_plugin_exclusions import PluginCacheGlobExclusions
30
+ from langchain_agentx.tools.search_path_resolver import (
31
+ policy_read_roots_from_engine,
32
+ resolve_search_root_for_prefixed_pattern,
33
+ )
30
34
  from langchain_agentx.utils.rg_executable import default_rg_executable
31
35
  from langchain_agentx.utils.unc_path import is_unc_path_skip_local_stat
32
36
  from langchain_agentx.workspace.tool_boundary import (
@@ -110,13 +114,28 @@ class GrepRuntimeTool(RuntimeTool):
110
114
  self, raw: dict[str, Any], ctx: ToolExecutionContext
111
115
  ) -> dict[str, Any]:
112
116
  data = dict(raw)
117
+ default_root = resolve_tool_cwd(ctx)
113
118
  path = data.get("path")
114
119
  if isinstance(path, str) and path.strip():
115
120
  data["path"] = self._path_normalizer.normalize_and_expand(
116
- path, base_dir=resolve_tool_cwd(ctx)
121
+ path, base_dir=default_root
117
122
  )
118
123
  else:
119
- data["path"] = resolve_tool_cwd(ctx)
124
+ glob_val = data.get("glob")
125
+ probe = (
126
+ str(glob_val).strip()
127
+ if isinstance(glob_val, str) and glob_val.strip()
128
+ else ""
129
+ )
130
+ if probe:
131
+ data["path"] = resolve_search_root_for_prefixed_pattern(
132
+ probe,
133
+ default_root,
134
+ ctx,
135
+ policy_read_roots=policy_read_roots_from_engine(self._policy),
136
+ )
137
+ else:
138
+ data["path"] = default_root
120
139
  pat = data.get("pattern")
121
140
  if isinstance(pat, str):
122
141
  data["pattern"] = pat.strip()
@@ -391,6 +410,7 @@ class GrepRuntimeTool(RuntimeTool):
391
410
  "mode": r.mode.value,
392
411
  "duration_ms": round(r.duration_ms, 2),
393
412
  "pattern": r.pattern,
413
+ "search_root": r.search_root,
394
414
  }
395
415
 
396
416
  if r.mode == GrepOutputMode.files_with_matches:
@@ -0,0 +1,132 @@
1
+ """
2
+ tools/search_path_resolver.py — repo 锚定 pattern 的 search_root 自动修正
3
+
4
+ 职责:
5
+ 相对 glob pattern 含静态目录前缀、且该前缀在默认 search_root 下不存在时,
6
+ 在 read_roots 上探测前缀目录并将 path 提升到匹配的 root(normalize 层)。
7
+
8
+ 链路位置:
9
+ GlobRuntimeTool.normalize_input(path 缺省且 pattern 含静态前缀)
10
+ GrepRuntimeTool.normalize_input(path 缺省且 glob 含静态前缀)
11
+
12
+ 裁剪:
13
+ 仅处理相对 pattern;绝对 pattern 仍由 invoke 内 extract_glob_base_directory 处理。
14
+ 前缀在 default_search_root 下已存在时不改写(避免误抬升 cwd 内局部搜索)。
15
+ 有 boundary_view 时仅探测 effective_read_roots,不合并 policy 超集(对齐 L1)。
16
+
17
+ 已知限制(P3 · 方案 A — 文档化,暂不改代码):
18
+ 多 read_root 同时满足 ``{root}/{prefix}`` 为目录时,取路径字符串最短的 root;
19
+ 长度并列时按探测顺序。典型单 repo / monorepo 父根部署无歧义;
20
+ 并列双 repo 同构目录树时需显式传 ``path``。见 docs/bug/glob-search-root-cwd-mismatch-2026-07-11.md §已知限制。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import os
26
+ from typing import TYPE_CHECKING, Any
27
+
28
+ from langchain_agentx.tools.glob.rg_pattern import extract_glob_base_directory
29
+ from langchain_agentx.workspace.tool_boundary import get_boundary_view
30
+
31
+ if TYPE_CHECKING:
32
+ from langchain_agentx.tool_runtime.models import ToolExecutionContext
33
+
34
+
35
+ def _join_root_and_prefix(root: str, base_dir: str) -> str:
36
+ parts = [p for p in base_dir.replace("\\", "/").split("/") if p and p != "."]
37
+ return os.path.join(root, *parts) if parts else root
38
+
39
+
40
+ def _prefix_dir_exists(root: str, base_dir: str) -> bool:
41
+ candidate = _join_root_and_prefix(root, base_dir)
42
+ return os.path.isdir(candidate)
43
+
44
+
45
+ def _collect_probe_read_roots(
46
+ ctx: ToolExecutionContext,
47
+ *,
48
+ policy_read_roots: list[str] | None,
49
+ default_search_root: str,
50
+ ) -> list[str]:
51
+ ordered: list[str] = []
52
+ seen: set[str] = set()
53
+
54
+ def add(root: str) -> None:
55
+ if not root:
56
+ return
57
+ real = os.path.realpath(root)
58
+ if real in seen:
59
+ return
60
+ seen.add(real)
61
+ ordered.append(real)
62
+
63
+ view = get_boundary_view(ctx)
64
+ if view is not None:
65
+ for root in view.effective_read_roots():
66
+ add(str(root))
67
+ else:
68
+ for root in policy_read_roots or []:
69
+ add(root)
70
+
71
+ add(default_search_root)
72
+ return ordered
73
+
74
+
75
+ def policy_read_roots_from_engine(policy: Any | None) -> list[str]:
76
+ """从 DefaultPolicyEngine(或兼容对象)读取 read_roots,供 normalize 探测。"""
77
+ if policy is None:
78
+ return []
79
+ roots = getattr(policy, "_read_roots", None)
80
+ if roots:
81
+ return [str(r) for r in roots]
82
+ config = getattr(policy, "_config", None)
83
+ if config is not None:
84
+ cfg_roots = getattr(config, "read_roots", None) or []
85
+ return [str(r) for r in cfg_roots]
86
+ return []
87
+
88
+
89
+ def resolve_search_root_for_prefixed_pattern(
90
+ pattern: str,
91
+ default_search_root: str,
92
+ ctx: ToolExecutionContext,
93
+ *,
94
+ policy_read_roots: list[str] | None = None,
95
+ ) -> str:
96
+ """
97
+ 当相对 pattern 的静态前缀在 default_search_root 下不存在时,
98
+ 在 read_roots 中查找使 ``{root}/{prefix}`` 为目录的最浅 root 并返回。
99
+
100
+ 多 root 同匹配时的 tie-break:当前取 ``len(root)`` 最小者(路径并列时按
101
+ ``_collect_probe_read_roots`` 顺序)。见模块头「已知限制(P3)」。
102
+ """
103
+ pat = pattern.strip()
104
+ if not pat or os.path.isabs(pat):
105
+ return default_search_root
106
+
107
+ base_dir, _ = extract_glob_base_directory(pat)
108
+ if not base_dir or base_dir in (".", "/"):
109
+ return default_search_root
110
+
111
+ default_real = os.path.realpath(default_search_root)
112
+ if _prefix_dir_exists(default_real, base_dir):
113
+ return default_search_root
114
+
115
+ best: str | None = None
116
+ for root in _collect_probe_read_roots(
117
+ ctx,
118
+ policy_read_roots=policy_read_roots,
119
+ default_search_root=default_search_root,
120
+ ):
121
+ if not _prefix_dir_exists(root, base_dir):
122
+ continue
123
+ if best is None or len(root) < len(best):
124
+ best = root
125
+
126
+ return best if best is not None else default_search_root
127
+
128
+
129
+ __all__ = [
130
+ "policy_read_roots_from_engine",
131
+ "resolve_search_root_for_prefixed_pattern",
132
+ ]
@@ -27,6 +27,11 @@ from typing import Any, ClassVar
27
27
  from ..loop.config.agent_config import merge_run_config
28
28
  from ..loop.run_context import get_run_context, register_spawned_task
29
29
  from ..loop.stream.loop_driver import LoopStreamDriver
30
+ from ..observability.events.finish_outcome import (
31
+ RecoveryPolicy,
32
+ RecoveryPolicyMode,
33
+ coerce_recovery_policy,
34
+ )
30
35
  from .runtime import WorkflowAgentRuntime, WorkflowGraphFactory
31
36
 
32
37
 
@@ -238,6 +243,7 @@ class LoopInvocation:
238
243
  session_id: str
239
244
  parent_conversation_session_id: str | None = None
240
245
  is_subagent: bool = False
246
+ recovery: RecoveryPolicy | None = None
241
247
 
242
248
  @classmethod
243
249
  def from_context(
@@ -247,6 +253,7 @@ class LoopInvocation:
247
253
  messages: list[Any],
248
254
  graph: Any,
249
255
  caller_config: dict[str, Any] | None = None,
256
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
250
257
  ) -> "LoopInvocation":
251
258
  session_id = WorkflowSessionIdPolicy.build_session_id(context)
252
259
  return cls(
@@ -259,6 +266,7 @@ class LoopInvocation:
259
266
  session_id=session_id,
260
267
  parent_conversation_session_id=context.parent_session_id,
261
268
  is_subagent=WorkflowSessionIdPolicy.is_subagent(context),
269
+ recovery=coerce_recovery_policy(recovery),
262
270
  )
263
271
 
264
272
 
@@ -295,6 +303,7 @@ class WorkflowAgentLoopExecutor:
295
303
  context: WorkflowNodeContext,
296
304
  messages: list[Any],
297
305
  caller_config: dict[str, Any] | None = None,
306
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
298
307
  ) -> LoopInvocation:
299
308
  graph_factory = self._require_graph_factory()
300
309
  runtime = WorkflowAgentRuntime(
@@ -308,6 +317,7 @@ class WorkflowAgentLoopExecutor:
308
317
  messages=messages,
309
318
  graph=graph,
310
319
  caller_config=caller_config,
320
+ recovery=recovery,
311
321
  )
312
322
 
313
323
  async def ainvoke(
@@ -316,11 +326,13 @@ class WorkflowAgentLoopExecutor:
316
326
  context: WorkflowNodeContext,
317
327
  messages: list[Any],
318
328
  caller_config: dict[str, Any] | None = None,
329
+ recovery: RecoveryPolicy | RecoveryPolicyMode | None = None,
319
330
  ) -> dict[str, Any]:
320
331
  invocation = self.build_invocation(
321
332
  context=context,
322
333
  messages=messages,
323
334
  caller_config=caller_config,
335
+ recovery=recovery,
324
336
  )
325
337
  return await self.run_invocation(
326
338
  invocation=invocation,
@@ -458,6 +470,7 @@ class WorkflowAgentLoopExecutor:
458
470
  version: str = "v2",
459
471
  ) -> AsyncIterator[dict[str, Any]]:
460
472
  del context
473
+ recovery_policy = coerce_recovery_policy(invocation.recovery)
461
474
 
462
475
  async def _run() -> AsyncIterator[dict[str, Any]]:
463
476
  async for event in driver.run(
@@ -465,6 +478,7 @@ class WorkflowAgentLoopExecutor:
465
478
  invocation.graph_input,
466
479
  config=invocation.run_config,
467
480
  version=version,
481
+ max_reactive_continue_per_drain=recovery_policy.reactive_continue_budget(),
468
482
  ):
469
483
  yield event
470
484
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: LangChain/LangGraph-based agent utilities for CodeBaseX.
5
5
  Author-email: GoodMood2008 <GoodMood2008@users.noreply.github.com>
6
6
  License: Apache License
@@ -1,4 +1,4 @@
1
- langchain_agentx/__init__.py,sha256=mIitE_209ZGQxVtUqOUCj_SxLxyx1fxbhZz6VZhlo_c,1678
1
+ langchain_agentx/__init__.py,sha256=CgHyU18uWVbXjHg_5d0L50y28LboDeAoFspC7YP5mk0,1678
2
2
  langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
3
3
  langchain_agentx/command/allowed_tools.py,sha256=TVA0VM8rm98H-QbLK_GRiX5RhEAZFxKawliMi4kk96A,3359
4
4
  langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
@@ -69,8 +69,8 @@ langchain_agentx/loop/exit/withheld_error.py,sha256=LfX3j7Rgd9VoMzlz7GOs9TPuRe7R
69
69
  langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
70
70
  langchain_agentx/loop/graph/builtin_loop_control.py,sha256=jagBvSo8fj6Yjl95rkp5A0W8RgyVf4P-EXMmGweURl8,7184
71
71
  langchain_agentx/loop/graph/factory.py,sha256=vo98tu_tbWI0s5q_fcCJgoN1yVQ5E2l0OG8LTJP9BZo,103009
72
- langchain_agentx/loop/graph/graph_edges.py,sha256=6mvLTRzTC9nArkrd5q5zGzRD_vKknZeInczJuBf9z-k,47994
73
- langchain_agentx/loop/graph/reactive_compact_node.py,sha256=AN31reohf25zRiqdPTmFCIQ1N6VwKn1oIkRkRuGAp4k,8725
72
+ langchain_agentx/loop/graph/graph_edges.py,sha256=W4n8xzXIz_G2n5nyMmHsMW2vMoGHLgeEtZNxNWwgiHg,47276
73
+ langchain_agentx/loop/graph/reactive_compact_node.py,sha256=633b5hTnR3BK8GBEKWhzOwGB0BQrVRKozNxDomWFjLo,8762
74
74
  langchain_agentx/loop/graph/runtime_tools_node.py,sha256=5iBPvHl2hOdfnRZrwtnoD4J_SzEuQKC6VQUJyPuthgA,5089
75
75
  langchain_agentx/loop/hook/__init__.py,sha256=rOjc1cqWBxrcp651w_SQX50dWY7o28L2c-r8iVs-SWU,2167
76
76
  langchain_agentx/loop/hook/async_hook_runner.py,sha256=SNyuYnQoowZzBTBHQb6o5L9wTG2Rl7qryeMtqFyEsxI,3195
@@ -98,7 +98,7 @@ langchain_agentx/loop/messages/transcript_codec.py,sha256=ib4Hybdu0Al-QSA7Ae5z4z
98
98
  langchain_agentx/loop/messages/types.py,sha256=h95GW0BSMf7P49AZKYDuT_lcdz2AgNKiBrabH6O30pI,3225
99
99
  langchain_agentx/loop/messages/validate.py,sha256=5OV7_DIlzHSz5gnhcnnYpAE6XiUYc67jtQJ1HmxlfAA,3844
100
100
  langchain_agentx/loop/model/__init__.py,sha256=8wOiwa2Yvh7QdR6ilhk4faI7_iwNT6QSEFgewJNhhOw,57
101
- langchain_agentx/loop/model/model_node.py,sha256=EWyWyHbKim39pqVZ40EB5on7Lv7jY65xNaUaLon06PQ,52229
101
+ langchain_agentx/loop/model/model_node.py,sha256=FY2_3Menv3ijfZvVt9sSrybN3QMWNRN2m0DX87wmNG8,53215
102
102
  langchain_agentx/loop/model/model_nodes.py,sha256=SqcPXJu4krh6IkrR1PHOMtaZPPZ0F09PY3_dtYtBhIk,29493
103
103
  langchain_agentx/loop/model/orphan_tool_results.py,sha256=TK8zDO63NYj0f0wDlREYN0qN8lpqnWRv9TMGDMQ3Z1Y,2493
104
104
  langchain_agentx/loop/model/retrier.py,sha256=S80QYWN-SfzyDx3gmhUcunFrMx4Khwi7K9wzynyTDQs,20877
@@ -132,8 +132,8 @@ langchain_agentx/loop/runtime/context_factory.py,sha256=w6VyDH5eQjMY_abZ4VDQpf_L
132
132
  langchain_agentx/loop/runtime/subagent_execution_paths.py,sha256=Bgyiom48uta1FZcnIE7fIxyrD-VFCRsw7s8IxIxmLb4,4033
133
133
  langchain_agentx/loop/stream/graph_driver.py,sha256=qMZHEw3wJ19Tfbz31-VPLy8Mt6e90xKougIjAoaEFCE,3935
134
134
  langchain_agentx/loop/stream/helpers.py,sha256=b2sqtwrAa5ic5B5AzNqItU8pmDHw13iGs3hBiShSIZk,1390
135
- langchain_agentx/loop/stream/loop_driver.py,sha256=wEYksr245wJtRMk8lnsVZPH-AAPdkGwQqjNJCcuZAdM,18405
136
- langchain_agentx/loop/stream/result.py,sha256=r_ht7xqTcYy-uv1ge_Ske-slyNs39JraBu-hSecdiqE,1093
135
+ langchain_agentx/loop/stream/loop_driver.py,sha256=_AMnczeTVDgCuQvZDE06ueWdos3-OPKHioz9XTOeZSE,21696
136
+ langchain_agentx/loop/stream/result.py,sha256=xQb9r1cxGCQpoAZ0gNo71CjhSE3eYOiYp5KnoOBzBF8,1131
137
137
  langchain_agentx/loop/stream/synthetic_events.py,sha256=Z3JT3iTuB9UQOoQXUprAYvaFV0vXEAuf5iqXYH9YSyI,2175
138
138
  langchain_agentx/loop/stream/transcript_gate.py,sha256=4AH3O0l8OrY_GQdqf-TOKOVS3selPLUC0jYLWQUTfkg,4690
139
139
  langchain_agentx/loop/subagent/__init__.py,sha256=MWa1IWt7eSdJhTnPCoIpvk5NMsPN6hknghqhX5kqwnY,2477
@@ -141,7 +141,7 @@ langchain_agentx/loop/subagent/active_run_registry.py,sha256=KurhAMDTBnRkdxNidaM
141
141
  langchain_agentx/loop/subagent/async_runner.py,sha256=y8Ke8nMi2XwdsrTKHCdOtIdr9hc-B1iGTAgMoVceapk,8692
142
142
  langchain_agentx/loop/subagent/boundary_snapshot.py,sha256=9cQuqN29nJDRnHnx9c8b09KRmODE2zv15Z06QyjVJps,10008
143
143
  langchain_agentx/loop/subagent/cache_safe_params.py,sha256=-qcgdazEYRy6s4ILQuttpxraE3gsxq17Ro7LYBnuv3E,4460
144
- langchain_agentx/loop/subagent/context.py,sha256=D4jl5u52jDMsPFb5IKaOgO742mR_Cxx7Z1bTciUrHzM,21572
144
+ langchain_agentx/loop/subagent/context.py,sha256=3ho_iZwHu3Ydz-4ZXJWCUeEuLe53VXAk8goBfdv0uiY,22290
145
145
  langchain_agentx/loop/subagent/display_projector.py,sha256=AKsGWS1c9reH0kuD5u4oQ24KsBgyt7X10iZM3ere6Fs,3504
146
146
  langchain_agentx/loop/subagent/fork_boilerplate.py,sha256=2Uh3cwegL7V-DvtLIxjX7ik5uW5mXfY2NBzP-biREso,5005
147
147
  langchain_agentx/loop/subagent/fork_ports.py,sha256=hZATCvlYOxNVZtR3PoOVbI1GkA0KhHEyux6lUIjG5_4,1323
@@ -153,7 +153,7 @@ langchain_agentx/loop/subagent/progress.py,sha256=_4YCDnVDH8sBiwXhCQVB8OHaUQwsCO
153
153
  langchain_agentx/loop/subagent/progress_dispatcher.py,sha256=e1Kd16WRUOAJSeAjupukrlxHoyCR_JO84SHqntMCzdQ,3831
154
154
  langchain_agentx/loop/subagent/prompt.py,sha256=p7RgzibNfEJdfX8nBnZGEwQLa3_EgOn4E56T9MBRIEc,1722
155
155
  langchain_agentx/loop/subagent/reasoning_extractor.py,sha256=KrCQtBdKzsjAEfA7H5COuXzBhuGGonT2Su8IEKYlUpA,1334
156
- langchain_agentx/loop/subagent/runner.py,sha256=_gFw8ULiLVoji0G1R-_fSdg6c-sVhJmB_ci8skiJ2Kk,28810
156
+ langchain_agentx/loop/subagent/runner.py,sha256=rjWtFKf2NgtLQax8nhlKQhlFVQuJeP8JvtuuhkJLbyM,29323
157
157
  langchain_agentx/loop/subagent/subagent_termination_classifier.py,sha256=ULArQeAibgy6ETSf4vRR0L6X1rsDM7-ZuLzRWQ_pmeY,3158
158
158
  langchain_agentx/loop/subagent/transcript.py,sha256=-S0VjZcvqlJQGkVGu8VDMTYwTwOePdjDHfjYi3uu7BY,7187
159
159
  langchain_agentx/loop/subagent/transcript_message_builder.py,sha256=gcDdbVOospJ9f0b-eM_0Y1uc4yTstaraWZdejokDyDQ,2081
@@ -201,7 +201,7 @@ langchain_agentx/memory/session/extractor.py,sha256=UUprKKtcMqn0pVhEWSnwCcc2tlB6
201
201
  langchain_agentx/memory/session/prompts.py,sha256=MA6UaNQsn9jrV78YuPbTTSKTmzvex_HY7kAaM9gVhUA,7690
202
202
  langchain_agentx/memory/session/session_memory.py,sha256=DdHkX-3rGOkyuxc3byq1hWNkHgucyo-n1jwUEWjxuP8,16642
203
203
  langchain_agentx/memory/session/tool_scope.py,sha256=DDhhzy_BEdZZe9SsisqZ9GOgMyt-aWUnCX-NH3QSPng,1684
204
- langchain_agentx/observability/__init__.py,sha256=IYPyGcf9WBN3qVUdxL7CNLf2sDN2FjlDzsWffnXmQ7I,2077
204
+ langchain_agentx/observability/__init__.py,sha256=UTV3th78Bw_b-Mo14EkxT7zpO_UoviHcZv17P5nCnY8,2177
205
205
  langchain_agentx/observability/evaluation/__init__.py,sha256=yqkw8noWqD-7p6xnhlF-M4F2FwYukS2XDpEYoXFNYZ4,430
206
206
  langchain_agentx/observability/evaluation/retention_scheduler.py,sha256=8iKw9G8jImIaKfmGhO9Xtm3GhvSICuGwEZp7GGczDNo,2250
207
207
  langchain_agentx/observability/evaluation/service.py,sha256=E0MZIBY67aVDooOYuri3_vTM_uguXJxCxNqQ5rJxFOg,3660
@@ -214,14 +214,14 @@ langchain_agentx/observability/evaluation/checkers/degradation.py,sha256=80-oENY
214
214
  langchain_agentx/observability/evaluation/checkers/exit_quality.py,sha256=kcV2VYLeavinm5-hysCVhNfIjv2Gj_m4Xt65B403FoY,1274
215
215
  langchain_agentx/observability/evaluation/checkers/session_memory.py,sha256=jYDdIDUAhYktggsymkBADBunrSbxdC66veM-tZt3a3s,1579
216
216
  langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8SeE9145H7ej4naahRcmgw_9st21dY1W3IQa2sg,2097
217
- langchain_agentx/observability/events/__init__.py,sha256=owI053qtlKN9N6dTr8e9DlM4pIuZkFjzE1RU6v1URZ4,951
217
+ langchain_agentx/observability/events/__init__.py,sha256=9IfY3IcALgQjxdsircWrwTCFuSwr8MTKBDstC8CXPHY,1051
218
218
  langchain_agentx/observability/events/accumulated_text_scope.py,sha256=M4OvuQTgReF8SFrOmr-M5vyOJpzfvxj_DKD5bVpGb70,3169
219
219
  langchain_agentx/observability/events/agent_defaults.py,sha256=gmX0P__lux5xbCb8dtAlVCb3wZ7avaqFleoW4Q5anw4,1187
220
220
  langchain_agentx/observability/events/agent_event_projector.py,sha256=RN1bS9-A-XI_AZYguraVtfcQ4OvEvDxnAWhn0NKySIU,60600
221
221
  langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
222
222
  langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
223
223
  langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=W4jLFy6868GX122qljjeLJowsG7g26o4eKVbHEa7DJc,11481
224
- langchain_agentx/observability/events/finish_outcome.py,sha256=qyrwYquIw-yHeBKuQr9NBTRKENfZMKTROiOV_IdbZx4,7942
224
+ langchain_agentx/observability/events/finish_outcome.py,sha256=I1-1RKQYMRXA5Kw0wDPXFpjdrGS421RM2fIL3gAY2kA,8926
225
225
  langchain_agentx/observability/events/langchain_agentx_event_adapter.py,sha256=plPZpoqYwDFR_XgdjH5NMQnl_oO6-rixBNl4sGJ0XRw,7519
226
226
  langchain_agentx/observability/events/model_end_text_backfill.py,sha256=7RBnxWLi1Oe-Bw9WTYqN-B6IdytFlY-Jso1STtQr6rE,5157
227
227
  langchain_agentx/observability/events/subagent_event_mapper.py,sha256=9M9cKzMz7_OXOod0JKtLb-FtUhAKiAVikKGRF6h6Ttk,6860
@@ -291,17 +291,17 @@ langchain_agentx/provider/semantics/families/openai_codec.py,sha256=RmzLq_LR1n3o
291
291
  langchain_agentx/provider/semantics/families/openai_finish_reason.py,sha256=t5ebGN2JMxUYPm8Jw8uebOgdAPc20GPgUiKJVja5r7U,1993
292
292
  langchain_agentx/provider/semantics/families/openai_interpreter.py,sha256=zT7EFfHJ5fmYZ_F59vDfyrYdEwcMJANxkoW1Pt3iocI,1915
293
293
  langchain_agentx/provider/semantics/families/openai_normalizer.py,sha256=oaf2Riz8EY6VQpc6SNKtsb-naMELw3gYV3IsnZoRBYU,2155
294
- langchain_agentx/session/__init__.py,sha256=7xrIOLhvbUynhKrkP5iEkJuRIyr2mAsph-o2WvUZFrg,1328
295
- langchain_agentx/session/agent_session.py,sha256=KW6wy1x5dwHvjCb7Khbq-XZ1_ResIMdTKkCYfnbF9Lg,24905
294
+ langchain_agentx/session/__init__.py,sha256=uXxjBzhW1FtvSfDTTaGZPbyeSFRzRQb2MfxDumKViug,1234
295
+ langchain_agentx/session/agent_session.py,sha256=bxjtuWDsWB3AK6Ij4jjnrCp6AtWn1NcS-2H5HieEWB0,23771
296
296
  langchain_agentx/session/cleanup_registry.py,sha256=aaPUn9psCwgZ27fB1qW7lzc1TeKaBvQAxhcHDiNs2Rs,4137
297
297
  langchain_agentx/session/conversation_factory.py,sha256=zsGBx9OwAOW21sqFsjK4d9mYLRRyxC5Bp1R9OCijxzI,5911
298
298
  langchain_agentx/session/conversation_recovery.py,sha256=OXzaxywRmqRwNA2Q1UNOpUvMlIjBzgdbsNPxPGHwoaA,5803
299
- langchain_agentx/session/conversation_session.py,sha256=_OjXq5FYA9HZCs0jrUszcxjyno8I-L7X8RMpPHsqiD8,16779
299
+ langchain_agentx/session/conversation_session.py,sha256=xHbZfor4ZR9l0ANCgkZ2FUnzpY3cE6afHxZn6ZeKqQg,17125
300
300
  langchain_agentx/session/factory.py,sha256=qzZF8EkELFKPzQ-fRI2C72Usnqk_hASWB1GM8_mYjZ8,6172
301
301
  langchain_agentx/session/plan_mode.py,sha256=GjPLBG6eVrTB7M-CeMwLqkHCQDdD93cCgwIAvOF0wRM,4710
302
302
  langchain_agentx/session/plan_mode_attachments.py,sha256=byIJC2F3RTJ9BTWVhpbE1e1rRwahAH5anPKsMXFvUE8,13861
303
303
  langchain_agentx/session/protocol.py,sha256=uNkNiQ2MD9ReJgPaYe5bF0x6JzVqv2TL-hAG8ME1Obs,622
304
- langchain_agentx/session/recovery.py,sha256=rcebj9uVBcUWwUOL9SS4nD_t0Mce9WlEQAgjATt-UEE,4476
304
+ langchain_agentx/session/recovery.py,sha256=vbBP7FAkp9Pcc15WzlxlApGH7ksQcwos2nVvOJD1Qn4,1802
305
305
  langchain_agentx/task_runtime/__init__.py,sha256=nwuVBYO0HDEvKMnjXbsyWlh1AC8fYBe79Y5nGgXWYbc,3972
306
306
  langchain_agentx/task_runtime/core/__init__.py,sha256=D5Y5TDp4oh7ml4O-sVdm8r-3-kEDJ7SJmPK7M6vy8_Y,1094
307
307
  langchain_agentx/task_runtime/core/ids.py,sha256=g19BWz6IK6YYyiuW3AU0HaaQGFjBzDue_ZCT8n_lip0,876
@@ -487,6 +487,7 @@ langchain_agentx/tool_runtime/resolvers/workflow.py,sha256=yahIZSShfF-eHUWAEfyxp
487
487
  langchain_agentx/tools/__init__.py,sha256=YUe8gX7o98qjgm0uURtjT9XqW_i0RCzhbjaKrkp1mj8,956
488
488
  langchain_agentx/tools/registry_names.py,sha256=kpQIPkP4eLX0sn7XpnVRdGS68AV6lu6c_4f930Rwr50,7231
489
489
  langchain_agentx/tools/ripgrep_plugin_exclusions.py,sha256=PoCJ26D--63gflqvwUZ_tI35xL1KMqzCbq5YjEtxJUM,5026
490
+ langchain_agentx/tools/search_path_resolver.py,sha256=DF3wZrUZT6t4ZmnBOsjldG740Cszqr9mtx-QkTng4F0,4484
490
491
  langchain_agentx/tools/agent/__init__.py,sha256=doBZ0Bym-CvcwIU8i-bC3jie4MBquFyFZUuoiSYFOCc,209
491
492
  langchain_agentx/tools/agent/backend.py,sha256=YKK2ttAaAxgN1rpeVe7V5h0eqSYG7kfm0x9Cr1lZL-c,3168
492
493
  langchain_agentx/tools/agent/builtin_subagent_loader.py,sha256=pIc-FcUrrnxjPmJT8lGy_xdgcMa-s6Tb39qpCQPaytw,3665
@@ -570,13 +571,13 @@ langchain_agentx/tools/glob/pagination.py,sha256=qn3HjCpvcARgXXRqhsQQ5huibPIu9Kc
570
571
  langchain_agentx/tools/glob/prompt.py,sha256=oPEXucn9P2z01ghXEtDpGGKR3KpOp6wH8PDDYaoiN6U,686
571
572
  langchain_agentx/tools/glob/rg_list_backend.py,sha256=gTyFQE0eYHl-7LkSdah76S8bQnLCgi-6ka6jixeSI4s,5006
572
573
  langchain_agentx/tools/glob/rg_pattern.py,sha256=YOV-1gdxC52vzdAwgz27l7mzPJvVuHqpqeWhiEnWQJs,1377
573
- langchain_agentx/tools/glob/tool.py,sha256=fNOUrt-YUnXXOODiHVAtS0xz9qxidFPuJ9DGRh-R1cY,14445
574
+ langchain_agentx/tools/glob/tool.py,sha256=m746Vl_aI9eFmrXYECkg16zMln9YPVxqjtijPlc5ALA,14868
574
575
  langchain_agentx/tools/grep/__init__.py,sha256=AA9qa42Ap9tw6lPQkNgKzYmr4hu44eP93qVdyP-eFNU,162
575
576
  langchain_agentx/tools/grep/backend.py,sha256=DSKetuS6Wcta5wrDBVFYSVDLsou0VDXXZyvdFvN1nls,13885
576
577
  langchain_agentx/tools/grep/models.py,sha256=6fTSux0COK06weQ7PWYqCGBQx60OaNtqC5q7xKtf5i0,3860
577
578
  langchain_agentx/tools/grep/prompt.py,sha256=Euo1NErphk5WHxaKXwJH-1v7joblfwEFWHIYNthKkzo,1336
578
579
  langchain_agentx/tools/grep/rg_subprocess_controller.py,sha256=ijMJnKPo7iEDFBz-_YipTiPusrL6OO2XbWxHUuwpMfA,4056
579
- langchain_agentx/tools/grep/tool.py,sha256=ngfLGdCrku_XfV_YfpoI3TyXiEqHcJ8wxcLz81mDMok,19795
580
+ langchain_agentx/tools/grep/tool.py,sha256=S_nGFqFgGHx9UhnXJdNvkdoHCJxLRZBDoPDd-Pxms0g,20513
580
581
  langchain_agentx/tools/plan_mode/__init__.py,sha256=wO5FX24xEB0Dx9h6bysBt1ql3wn0rjPYsg70mbl4S_0,402
581
582
  langchain_agentx/tools/plan_mode/constants.py,sha256=vBjcx9JTSOhU9jfCJ4RKnCxVQPn0HzHapouKxRf2Zns,293
582
583
  langchain_agentx/tools/plan_mode/models.py,sha256=kRHPJetlB9X5nAq7-bWm1WcC68YsAj17UxY7fR6TpZ4,1204
@@ -692,7 +693,7 @@ langchain_agentx/workflow/catalog_registry.py,sha256=tYUny5FkHwQU-RRUxOdxaJhiRV_
692
693
  langchain_agentx/workflow/dag.py,sha256=Y1IR1IwnM79Xuev6HZnOvAE3Q23MRnA9lu4vg2Cie48,4469
693
694
  langchain_agentx/workflow/display_enricher.py,sha256=2HJ4xJI8H4hwBoWHAi6D_LwE-dcbZD_pafnS7DMDYZ4,6147
694
695
  langchain_agentx/workflow/display_resolver.py,sha256=hEaljqprjubG82zeqLOEeQ32AMAxaZwvwaLq3v-yt3c,10119
695
- langchain_agentx/workflow/execution.py,sha256=drOyyBooNcGDPfGAqHIMOTkwOGB4oNlvagVV2Co7mrg,18299
696
+ langchain_agentx/workflow/execution.py,sha256=iCEkPgLkaoEM4cB6o7KAqIaBpFuY5HtGGISMNm0tr4A,18954
696
697
  langchain_agentx/workflow/loop_factory.py,sha256=hCShf_gDnesW2A9JspaUY3upp3NCIguII_wRiXnSIUM,3954
697
698
  langchain_agentx/workflow/memory_drain_registry.py,sha256=Xm_ytbB-BbRIu3-h-KhptsCsmmueOiNZePUuG1hk8zk,1681
698
699
  langchain_agentx/workflow/memory_session_policy.py,sha256=2FI0ZbAA6MnxJexnLYLadQpiqipPQwVnUrTWgl55A74,3333
@@ -751,8 +752,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
751
752
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
752
753
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
753
754
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
754
- langchain_agentx_python-2.0.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
755
- langchain_agentx_python-2.0.1.dist-info/METADATA,sha256=Md4WdCIDe34uD_fT4C8MrwhHW_yXKLNfIsgKvFU9Bv0,24844
756
- langchain_agentx_python-2.0.1.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
757
- langchain_agentx_python-2.0.1.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
758
- langchain_agentx_python-2.0.1.dist-info/RECORD,,
755
+ langchain_agentx_python-2.1.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
756
+ langchain_agentx_python-2.1.0.dist-info/METADATA,sha256=TdLyMypNyg3psJwPdU3votemXhPzWi1HQkEH7qm03Ww,24844
757
+ langchain_agentx_python-2.1.0.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
758
+ langchain_agentx_python-2.1.0.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
759
+ langchain_agentx_python-2.1.0.dist-info/RECORD,,