langchain-agentx-python 2.4.0__py3-none-any.whl → 2.4.1__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/config/loop_config.py +2 -0
- langchain_agentx/loop/graph/factory.py +6 -1
- langchain_agentx/loop/run_context.py +48 -0
- langchain_agentx/loop/runtime/context_factory.py +7 -1
- langchain_agentx/loop/subagent/async_runner.py +12 -1
- langchain_agentx/loop/subagent/graph.py +2 -0
- langchain_agentx/loop/subagent/orchestrator.py +510 -366
- langchain_agentx/loop/subagent/runner.py +7 -5
- langchain_agentx/tool_runtime/adapter.py +5 -0
- langchain_agentx/tool_runtime/models.py +4 -1
- langchain_agentx/tools/agent/backend.py +33 -11
- langchain_agentx/tools/agent/limits.py +28 -3
- langchain_agentx/tools/agent/loader.py +2 -1
- langchain_agentx/tools/agent/nesting_policy.py +65 -0
- langchain_agentx/tools/agent/scope.py +16 -15
- langchain_agentx/tools/agent/tool.py +14 -1
- {langchain_agentx_python-2.4.0.dist-info → langchain_agentx_python-2.4.1.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.4.0.dist-info → langchain_agentx_python-2.4.1.dist-info}/RECORD +22 -21
- {langchain_agentx_python-2.4.0.dist-info → langchain_agentx_python-2.4.1.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.4.0.dist-info → langchain_agentx_python-2.4.1.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.4.0.dist-info → langchain_agentx_python-2.4.1.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -136,6 +136,8 @@ class SubagentLinkage:
|
|
|
136
136
|
subagent_type: str | None = None
|
|
137
137
|
agent_depth: int = 0
|
|
138
138
|
parent_agent_id: str | None = None
|
|
139
|
+
fork_from_parent: bool = False
|
|
140
|
+
"""True = 当前子 loop 为 ForkA worker(会话继承 fork);供 ToolExecutionContext / L1。"""
|
|
139
141
|
|
|
140
142
|
|
|
141
143
|
@dataclass(frozen=True, init=False)
|
|
@@ -312,7 +312,12 @@ def _flatten_config_for_runtime(config: AgentLoopConfig) -> _FlatLoopConfig:
|
|
|
312
312
|
conversation_session_id=config.conversation_session_id,
|
|
313
313
|
agent_id=config.agent_id,
|
|
314
314
|
parent_agent_id=config.subagent.parent_agent_id,
|
|
315
|
-
agent_depth=
|
|
315
|
+
agent_depth=(
|
|
316
|
+
config.subagent.agent_depth
|
|
317
|
+
if config.subagent.is_subagent
|
|
318
|
+
else config.agent_depth
|
|
319
|
+
),
|
|
320
|
+
fork_from_parent=bool(config.subagent.fork_from_parent),
|
|
316
321
|
subagent_type=config.subagent.subagent_type,
|
|
317
322
|
trace_enabled=config.trace.enabled,
|
|
318
323
|
trace_persist=config.trace.persist,
|
|
@@ -112,6 +112,36 @@ async def run_under_owner_run_context(
|
|
|
112
112
|
_current_run_context.reset(token)
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
@dataclass
|
|
116
|
+
class SubagentConcurrencyGate:
|
|
117
|
+
"""会话级并发子 Agent 计数(挂在 RunContext.cancel_authority 根上)。
|
|
118
|
+
|
|
119
|
+
Doc-37 C9 / Phase 2:独立于深度;无「总 spawn」帽。
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
_running: int = field(default=0, repr=False)
|
|
123
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
|
|
124
|
+
|
|
125
|
+
def try_acquire(self, *, limit: int) -> bool:
|
|
126
|
+
if limit < 1:
|
|
127
|
+
return False
|
|
128
|
+
with self._lock:
|
|
129
|
+
if self._running >= limit:
|
|
130
|
+
return False
|
|
131
|
+
self._running += 1
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
def release(self) -> None:
|
|
135
|
+
with self._lock:
|
|
136
|
+
if self._running > 0:
|
|
137
|
+
self._running -= 1
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def running(self) -> int:
|
|
141
|
+
with self._lock:
|
|
142
|
+
return self._running
|
|
143
|
+
|
|
144
|
+
|
|
115
145
|
@dataclass
|
|
116
146
|
class RunContext:
|
|
117
147
|
"""运行上下文(Graph/Workflow/Agent 共享)。"""
|
|
@@ -127,6 +157,10 @@ class RunContext:
|
|
|
127
157
|
# D-EVT-6:宿主在 cancel_tree / killAll 时写入;finish 投影读取;缺省 unknown。
|
|
128
158
|
# 取值:escape | workflow_stop | kill_agents | unknown;禁止从「是否有后台」猜测。
|
|
129
159
|
cancel_source: str | None = None
|
|
160
|
+
# Phase 2:并发子 Agent 槽位;acquire/release 始终走 cancel_authority 根上的 gate。
|
|
161
|
+
subagent_concurrency: SubagentConcurrencyGate = field(
|
|
162
|
+
default_factory=SubagentConcurrencyGate, repr=False
|
|
163
|
+
)
|
|
130
164
|
# install 时若成功 register_agent_loop,记下以便 reset 精确 unregister(勿误弹外层)。
|
|
131
165
|
_registered_agent_loop: asyncio.AbstractEventLoop | None = field(
|
|
132
166
|
default=None, repr=False, compare=False
|
|
@@ -134,6 +168,18 @@ class RunContext:
|
|
|
134
168
|
# install 时是否 push 进进程级 RunContext 栈(供 worker 解析 owner)。
|
|
135
169
|
_registered_on_ctx_stack: bool = field(default=False, repr=False, compare=False)
|
|
136
170
|
|
|
171
|
+
def try_acquire_subagent_slot(self, *, limit: int) -> bool:
|
|
172
|
+
"""在 outermost RunContext 上占用一个并发槽;满则 False。"""
|
|
173
|
+
return self.cancel_authority().subagent_concurrency.try_acquire(limit=limit)
|
|
174
|
+
|
|
175
|
+
def release_subagent_slot(self) -> None:
|
|
176
|
+
"""释放 outermost 上的一个并发槽(幂等下限 0)。"""
|
|
177
|
+
self.cancel_authority().subagent_concurrency.release()
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def running_subagent_count(self) -> int:
|
|
181
|
+
return self.cancel_authority().subagent_concurrency.running
|
|
182
|
+
|
|
137
183
|
def register_child(self, task: asyncio.Task) -> None:
|
|
138
184
|
self.child_tasks.add(task)
|
|
139
185
|
|
|
@@ -412,6 +458,7 @@ def register_spawned_thread(
|
|
|
412
458
|
|
|
413
459
|
__all__ = [
|
|
414
460
|
"RunContext",
|
|
461
|
+
"SubagentConcurrencyGate",
|
|
415
462
|
"create_task_with_copied_context",
|
|
416
463
|
"drive_async_iterator_in_current_task",
|
|
417
464
|
"get_run_context",
|
|
@@ -420,5 +467,6 @@ __all__ = [
|
|
|
420
467
|
"register_spawned_thread",
|
|
421
468
|
"reset_run_context",
|
|
422
469
|
"resolve_cancel_authority",
|
|
470
|
+
"resolve_owner_run_context",
|
|
423
471
|
"run_context_scope",
|
|
424
472
|
]
|
|
@@ -146,12 +146,15 @@ class RuntimeContextFactory:
|
|
|
146
146
|
subagent_query_source = (
|
|
147
147
|
f"agent:{subagent_type}" if subagent_type else "agent:default"
|
|
148
148
|
)
|
|
149
|
+
# 子 depth 与顶层 agent_depth 必须一致:factory flatten 在 is_subagent 时
|
|
150
|
+
# 优先读 subagent.agent_depth(嵌套 L1→L2 若只写顶层会误成 0)。
|
|
151
|
+
child_depth = int(getattr(parent_ctx, "agent_depth", 0) or 0) + 1
|
|
149
152
|
|
|
150
153
|
return AgentLoopConfig(
|
|
151
154
|
model=resolved_model,
|
|
152
155
|
session_id=getattr(parent_ctx, "session_id", "") or "",
|
|
153
156
|
agent_id=agent_id,
|
|
154
|
-
agent_depth=
|
|
157
|
+
agent_depth=child_depth,
|
|
155
158
|
max_steps=resolved_max_steps,
|
|
156
159
|
permission_mode=resolved_permission,
|
|
157
160
|
model_retry=ModelRetrySettings(query_source=subagent_query_source),
|
|
@@ -171,6 +174,9 @@ class RuntimeContextFactory:
|
|
|
171
174
|
),
|
|
172
175
|
subagent=SubagentLinkage(
|
|
173
176
|
is_subagent=True,
|
|
177
|
+
subagent_type=str(subagent_type) if subagent_type else None,
|
|
174
178
|
parent_agent_id=parent_agent_id,
|
|
179
|
+
agent_depth=child_depth,
|
|
180
|
+
# fork_from_parent 由 orchestrator mode 决定,derive 不知 mode → False
|
|
175
181
|
),
|
|
176
182
|
)
|
|
@@ -175,8 +175,12 @@ async def _execute_background_lifecycle(
|
|
|
175
175
|
task_id: str,
|
|
176
176
|
task_runtime: Any | None,
|
|
177
177
|
allowed_tool_names: set[str] | None = None,
|
|
178
|
+
slot_owner: RunContext | None = None,
|
|
178
179
|
) -> None:
|
|
179
|
-
"""共享后台生命周期:run_subagent → 写 output → task_runtime 终态。
|
|
180
|
+
"""共享后台生命周期:run_subagent → 写 output → task_runtime 终态。
|
|
181
|
+
|
|
182
|
+
``slot_owner``:Phase 2 并发槽从 orchestrator 移交至此;终态时 release。
|
|
183
|
+
"""
|
|
180
184
|
execution_ctx = ctx
|
|
181
185
|
execution_ctx.is_async = False
|
|
182
186
|
agent_id = getattr(ctx, "agent_id", None)
|
|
@@ -252,6 +256,9 @@ async def _execute_background_lifecycle(
|
|
|
252
256
|
agent_id=agent_id,
|
|
253
257
|
terminal_reason=TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
|
|
254
258
|
)
|
|
259
|
+
finally:
|
|
260
|
+
if slot_owner is not None:
|
|
261
|
+
slot_owner.release_subagent_slot()
|
|
255
262
|
|
|
256
263
|
|
|
257
264
|
def register_background_agent(
|
|
@@ -264,6 +271,7 @@ def register_background_agent(
|
|
|
264
271
|
parent_ctx: Any | None = None,
|
|
265
272
|
owner_run_ctx: RunContext | None = None,
|
|
266
273
|
allowed_tool_names: set[str] | None = None,
|
|
274
|
+
slot_owner: RunContext | None = None,
|
|
267
275
|
) -> BackgroundAgentHandle:
|
|
268
276
|
"""注册并启动后台子 Agent(仅同 loop C3;无/异 loop 拒绝,D-BG-1)。"""
|
|
269
277
|
task_runtime = _resolve_task_runtime(parent_ctx)
|
|
@@ -307,6 +315,7 @@ def register_background_agent(
|
|
|
307
315
|
task_runtime=task_runtime,
|
|
308
316
|
owner_run_ctx=owner_run_ctx,
|
|
309
317
|
allowed_tool_names=allowed_tool_names,
|
|
318
|
+
slot_owner=slot_owner,
|
|
310
319
|
)
|
|
311
320
|
|
|
312
321
|
|
|
@@ -321,6 +330,7 @@ def _register_background_agent_task(
|
|
|
321
330
|
task_runtime: Any | None,
|
|
322
331
|
owner_run_ctx: RunContext | None,
|
|
323
332
|
allowed_tool_names: set[str] | None = None,
|
|
333
|
+
slot_owner: RunContext | None = None,
|
|
324
334
|
) -> BackgroundAgentHandle:
|
|
325
335
|
async def _task_body() -> None:
|
|
326
336
|
await _execute_background_lifecycle(
|
|
@@ -331,6 +341,7 @@ def _register_background_agent_task(
|
|
|
331
341
|
task_id=task_id,
|
|
332
342
|
task_runtime=task_runtime,
|
|
333
343
|
allowed_tool_names=allowed_tool_names,
|
|
344
|
+
slot_owner=slot_owner,
|
|
334
345
|
)
|
|
335
346
|
|
|
336
347
|
task = loop.create_task(_task_body(), name=f"agentx-subagent-{task_id}")
|
|
@@ -119,11 +119,13 @@ def create_subagent_graph(
|
|
|
119
119
|
parent_tool_call_id=ctx.parent_tool_call_id,
|
|
120
120
|
parent_conversation_session_id=ctx.parent_conversation_session_id,
|
|
121
121
|
),
|
|
122
|
+
agent_depth=ctx.agent_depth,
|
|
122
123
|
subagent=SubagentLinkage(
|
|
123
124
|
is_subagent=True,
|
|
124
125
|
subagent_type=ctx.subagent_type,
|
|
125
126
|
parent_agent_id=ctx.parent_agent_id,
|
|
126
127
|
agent_depth=ctx.agent_depth,
|
|
128
|
+
fork_from_parent=bool(ctx.fork_from_parent),
|
|
127
129
|
),
|
|
128
130
|
max_steps=ctx.max_steps,
|
|
129
131
|
headless=bool(ctx.headless),
|