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.
- langchain_agentx/__init__.py +1 -1
- langchain_agentx/loop/graph/graph_edges.py +2 -18
- langchain_agentx/loop/graph/reactive_compact_node.py +1 -0
- langchain_agentx/loop/model/model_node.py +23 -0
- langchain_agentx/loop/stream/loop_driver.py +162 -69
- langchain_agentx/loop/stream/result.py +1 -0
- langchain_agentx/loop/subagent/context.py +19 -0
- langchain_agentx/loop/subagent/runner.py +12 -0
- langchain_agentx/observability/__init__.py +4 -0
- langchain_agentx/observability/events/__init__.py +4 -0
- langchain_agentx/observability/events/finish_outcome.py +30 -0
- langchain_agentx/session/__init__.py +2 -6
- langchain_agentx/session/agent_session.py +10 -35
- langchain_agentx/session/conversation_session.py +8 -0
- langchain_agentx/session/recovery.py +6 -86
- langchain_agentx/tools/glob/tool.py +13 -2
- langchain_agentx/tools/grep/tool.py +22 -2
- langchain_agentx/tools/search_path_resolver.py +132 -0
- langchain_agentx/workflow/execution.py +14 -0
- {langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/RECORD +24 -23
- {langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -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"),
|
|
@@ -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
|
-
|
|
71
|
-
async for event in
|
|
72
|
-
|
|
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.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
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 "")
|
|
@@ -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
|
-
|
|
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
|
|
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"
|
|
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 —
|
|
2
|
+
session/recovery.py — Stream closeout helpers for AgentSession.
|
|
3
3
|
|
|
4
4
|
职责:
|
|
5
|
-
|
|
6
|
-
避免消费方重复 append 同一条 HumanMessage。
|
|
5
|
+
从 graph output / LoopStreamDriver.result 构建 finish_outcome 字典。
|
|
7
6
|
|
|
8
7
|
链路位置:
|
|
9
|
-
AgentSession.
|
|
8
|
+
AgentSession.stream_loop_events closeout → 本模块
|
|
10
9
|
|
|
11
10
|
当前裁剪范围:
|
|
12
|
-
|
|
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
|
|
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=
|
|
140
|
+
path, base_dir=default_root
|
|
135
141
|
)
|
|
136
142
|
else:
|
|
137
|
-
data["path"] =
|
|
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=
|
|
121
|
+
path, base_dir=default_root
|
|
117
122
|
)
|
|
118
123
|
else:
|
|
119
|
-
|
|
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,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
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=
|
|
73
|
-
langchain_agentx/loop/graph/reactive_compact_node.py,sha256=
|
|
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=
|
|
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=
|
|
136
|
-
langchain_agentx/loop/stream/result.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
295
|
-
langchain_agentx/session/agent_session.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
755
|
-
langchain_agentx_python-2.0.
|
|
756
|
-
langchain_agentx_python-2.0.
|
|
757
|
-
langchain_agentx_python-2.0.
|
|
758
|
-
langchain_agentx_python-2.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.0.1.dist-info → langchain_agentx_python-2.1.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|