langchain-agentx-python 2.3.9__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 +186 -23
- langchain_agentx/loop/runtime/context_factory.py +7 -1
- langchain_agentx/loop/stream/loop_driver.py +5 -0
- langchain_agentx/loop/subagent/async_runner.py +12 -1
- langchain_agentx/loop/subagent/graph.py +2 -0
- langchain_agentx/loop/subagent/orchestrator.py +515 -366
- langchain_agentx/loop/subagent/runner.py +43 -16
- 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/tools/bash/ast_security.py +19 -1
- langchain_agentx/tools/bash/command_allow_rules.py +27 -0
- langchain_agentx/tools/bash/read_only_validation.py +40 -2
- langchain_agentx/tools/skill/fork.py +105 -19
- langchain_agentx/tools/skill/tool.py +52 -0
- langchain_agentx/workspace/project_memory_layout.py +2 -1
- {langchain_agentx_python-2.3.9.dist-info → langchain_agentx_python-2.4.1.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.3.9.dist-info → langchain_agentx_python-2.4.1.dist-info}/RECORD +29 -28
- {langchain_agentx_python-2.3.9.dist-info → langchain_agentx_python-2.4.1.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.3.9.dist-info → langchain_agentx_python-2.4.1.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.3.9.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,
|
|
@@ -13,6 +13,8 @@ run_context.py — Workflow/Agent 结构化并发上下文(parent → child ca
|
|
|
13
13
|
P1:reset 失败时在仍可见 owner 的 derived logical context 内 force-clear;
|
|
14
14
|
宿主 detached ingest 用 create_task_with_copied_context / run_context_scope。
|
|
15
15
|
nested Parallelization:child_contexts + 递归 cancel_tree;W4 可用 use_cancel_authority。
|
|
16
|
+
agent loop 登记:供 skill fork 同步桥 ``run_coroutine_threadsafe`` 解析父 loop
|
|
17
|
+
(ContextVar 在 ThreadPoolExecutor 上常丢失,故用进程级 stack)。
|
|
16
18
|
"""
|
|
17
19
|
|
|
18
20
|
from __future__ import annotations
|
|
@@ -34,6 +36,111 @@ _current_run_context: ContextVar[Optional["RunContext"]] = ContextVar(
|
|
|
34
36
|
"run_ctx", default=None
|
|
35
37
|
)
|
|
36
38
|
|
|
39
|
+
# 进程级父 agent loop / RunContext 栈:ThreadPoolExecutor worker 看不见 ContextVar,
|
|
40
|
+
# 须 threadsafe 回父 loop(bug4)并恢复 owner 供 cancel 登记(skill-fork cancel orphan)。
|
|
41
|
+
_agent_loop_lock = threading.Lock()
|
|
42
|
+
_agent_loop_stack: list[asyncio.AbstractEventLoop] = []
|
|
43
|
+
_agent_run_ctx_stack: list["RunContext"] = []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def register_agent_loop(loop: asyncio.AbstractEventLoop) -> None:
|
|
47
|
+
"""登记当前存活的 agent/session event loop(可嵌套;同 loop 可重复 push)。"""
|
|
48
|
+
with _agent_loop_lock:
|
|
49
|
+
_agent_loop_stack.append(loop)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def unregister_agent_loop(loop: asyncio.AbstractEventLoop) -> None:
|
|
53
|
+
"""弹出最近一次与 ``loop`` 相同的登记(LIFO;未见则 no-op)。"""
|
|
54
|
+
with _agent_loop_lock:
|
|
55
|
+
for index in range(len(_agent_loop_stack) - 1, -1, -1):
|
|
56
|
+
if _agent_loop_stack[index] is loop:
|
|
57
|
+
del _agent_loop_stack[index]
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_agent_loop(
|
|
62
|
+
*,
|
|
63
|
+
owner: RunContext | None = None,
|
|
64
|
+
) -> asyncio.AbstractEventLoop | None:
|
|
65
|
+
"""解析仍在跑的父 agent loop;优先 ``owner.root_task``,否则栈顶存活项。"""
|
|
66
|
+
if owner is not None:
|
|
67
|
+
try:
|
|
68
|
+
loop = owner.root_task.get_loop()
|
|
69
|
+
if not loop.is_closed() and loop.is_running():
|
|
70
|
+
return loop
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
with _agent_loop_lock:
|
|
74
|
+
for loop in reversed(_agent_loop_stack):
|
|
75
|
+
try:
|
|
76
|
+
if not loop.is_closed() and loop.is_running():
|
|
77
|
+
return loop
|
|
78
|
+
except Exception:
|
|
79
|
+
continue
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def resolve_owner_run_context(
|
|
84
|
+
owner: RunContext | None = None,
|
|
85
|
+
) -> RunContext | None:
|
|
86
|
+
"""解析取消挂接用的 owner:显式参数 → ContextVar → 进程级 install 栈顶。"""
|
|
87
|
+
if owner is not None:
|
|
88
|
+
return owner
|
|
89
|
+
current = get_run_context()
|
|
90
|
+
if current is not None:
|
|
91
|
+
return current
|
|
92
|
+
with _agent_loop_lock:
|
|
93
|
+
if _agent_run_ctx_stack:
|
|
94
|
+
return _agent_run_ctx_stack[-1]
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def run_under_owner_run_context(
|
|
99
|
+
coro: Awaitable[T],
|
|
100
|
+
*,
|
|
101
|
+
owner: RunContext | None,
|
|
102
|
+
) -> T:
|
|
103
|
+
"""在指定 owner 的 ContextVar 下 await(threadsafe 桥恢复父 RunContext)。"""
|
|
104
|
+
if owner is None:
|
|
105
|
+
return await coro
|
|
106
|
+
if get_run_context() is owner:
|
|
107
|
+
return await coro
|
|
108
|
+
token = _current_run_context.set(owner)
|
|
109
|
+
try:
|
|
110
|
+
return await coro
|
|
111
|
+
finally:
|
|
112
|
+
_current_run_context.reset(token)
|
|
113
|
+
|
|
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
|
+
|
|
37
144
|
|
|
38
145
|
@dataclass
|
|
39
146
|
class RunContext:
|
|
@@ -50,6 +157,28 @@ class RunContext:
|
|
|
50
157
|
# D-EVT-6:宿主在 cancel_tree / killAll 时写入;finish 投影读取;缺省 unknown。
|
|
51
158
|
# 取值:escape | workflow_stop | kill_agents | unknown;禁止从「是否有后台」猜测。
|
|
52
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
|
+
)
|
|
164
|
+
# install 时若成功 register_agent_loop,记下以便 reset 精确 unregister(勿误弹外层)。
|
|
165
|
+
_registered_agent_loop: asyncio.AbstractEventLoop | None = field(
|
|
166
|
+
default=None, repr=False, compare=False
|
|
167
|
+
)
|
|
168
|
+
# install 时是否 push 进进程级 RunContext 栈(供 worker 解析 owner)。
|
|
169
|
+
_registered_on_ctx_stack: bool = field(default=False, repr=False, compare=False)
|
|
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
|
|
53
182
|
|
|
54
183
|
def register_child(self, task: asyncio.Task) -> None:
|
|
55
184
|
self.child_tasks.add(task)
|
|
@@ -167,6 +296,18 @@ def install_run_context(
|
|
|
167
296
|
parent.register_child(ctx.root_task)
|
|
168
297
|
parent.register_child_context(ctx)
|
|
169
298
|
token = _current_run_context.set(ctx)
|
|
299
|
+
try:
|
|
300
|
+
loop = ctx.root_task.get_loop()
|
|
301
|
+
register_agent_loop(loop)
|
|
302
|
+
ctx._registered_agent_loop = loop
|
|
303
|
+
except Exception:
|
|
304
|
+
logger.debug(
|
|
305
|
+
"register_agent_loop skipped at install_run_context",
|
|
306
|
+
exc_info=True,
|
|
307
|
+
)
|
|
308
|
+
with _agent_loop_lock:
|
|
309
|
+
_agent_run_ctx_stack.append(ctx)
|
|
310
|
+
ctx._registered_on_ctx_stack = True
|
|
170
311
|
return ctx, token
|
|
171
312
|
|
|
172
313
|
|
|
@@ -177,33 +318,53 @@ def reset_run_context(token: Token, *, owner: RunContext | None = None) -> None:
|
|
|
177
318
|
P1:若当前 logical context 仍可见 ``owner``(derived context 继承值但 token 无效),
|
|
178
319
|
显式 ``set(None)`` 清理本层,避免 workflow finally 后 run_ctx 悬挂。
|
|
179
320
|
"""
|
|
321
|
+
source = owner if owner is not None else get_run_context()
|
|
322
|
+
loop_to_unreg: asyncio.AbstractEventLoop | None = None
|
|
323
|
+
ctx_to_unreg: RunContext | None = None
|
|
324
|
+
if source is not None and source._registered_agent_loop is not None:
|
|
325
|
+
loop_to_unreg = source._registered_agent_loop
|
|
326
|
+
source._registered_agent_loop = None
|
|
327
|
+
if source is not None and source._registered_on_ctx_stack:
|
|
328
|
+
ctx_to_unreg = source
|
|
329
|
+
source._registered_on_ctx_stack = False
|
|
330
|
+
|
|
180
331
|
try:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
332
|
+
try:
|
|
333
|
+
_current_run_context.reset(token)
|
|
334
|
+
return
|
|
335
|
+
except ValueError:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
current = get_run_context()
|
|
339
|
+
if owner is not None and current is owner:
|
|
340
|
+
_current_run_context.set(None)
|
|
341
|
+
logger.warning(
|
|
342
|
+
"run_ctx force-cleared: token reset failed in derived logical context "
|
|
343
|
+
"(task=%r)",
|
|
344
|
+
owner.root_task.get_name(),
|
|
345
|
+
)
|
|
346
|
+
return
|
|
185
347
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
owner.root_task.get_name(),
|
|
193
|
-
)
|
|
194
|
-
return
|
|
348
|
+
if owner is not None and owner.root_task.done():
|
|
349
|
+
logger.debug(
|
|
350
|
+
"run_ctx reset skipped: owner task %r already done; context discarded",
|
|
351
|
+
owner.root_task.get_name(),
|
|
352
|
+
)
|
|
353
|
+
return
|
|
195
354
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
owner.root_task.get_name(),
|
|
355
|
+
logger.warning(
|
|
356
|
+
"run_ctx reset skipped: token created in a different context",
|
|
357
|
+
exc_info=True,
|
|
200
358
|
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
359
|
+
finally:
|
|
360
|
+
if loop_to_unreg is not None:
|
|
361
|
+
unregister_agent_loop(loop_to_unreg)
|
|
362
|
+
if ctx_to_unreg is not None:
|
|
363
|
+
with _agent_loop_lock:
|
|
364
|
+
for index in range(len(_agent_run_ctx_stack) - 1, -1, -1):
|
|
365
|
+
if _agent_run_ctx_stack[index] is ctx_to_unreg:
|
|
366
|
+
del _agent_run_ctx_stack[index]
|
|
367
|
+
break
|
|
207
368
|
|
|
208
369
|
|
|
209
370
|
@asynccontextmanager
|
|
@@ -297,6 +458,7 @@ def register_spawned_thread(
|
|
|
297
458
|
|
|
298
459
|
__all__ = [
|
|
299
460
|
"RunContext",
|
|
461
|
+
"SubagentConcurrencyGate",
|
|
300
462
|
"create_task_with_copied_context",
|
|
301
463
|
"drive_async_iterator_in_current_task",
|
|
302
464
|
"get_run_context",
|
|
@@ -305,5 +467,6 @@ __all__ = [
|
|
|
305
467
|
"register_spawned_thread",
|
|
306
468
|
"reset_run_context",
|
|
307
469
|
"resolve_cancel_authority",
|
|
470
|
+
"resolve_owner_run_context",
|
|
308
471
|
"run_context_scope",
|
|
309
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
|
)
|
|
@@ -11,6 +11,7 @@ loop_driver.py — Loop-aware shared stream driver.
|
|
|
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
13
|
R1 反应恢复:单次 drain 内 budgeted continue 重入(S2,对齐 CC queryLoop continue)。
|
|
14
|
+
进入/退出时登记 agent loop,供 Ask 路径无 Workflow RunContext 时 fork 同步桥解析。
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
from __future__ import annotations
|
|
@@ -22,6 +23,7 @@ from typing import Any, AsyncIterator, Literal
|
|
|
22
23
|
from langgraph.errors import GraphRecursionError
|
|
23
24
|
|
|
24
25
|
from langchain_agentx.loop.loop_abort import LoopAbortSignal, is_cancel_requested
|
|
26
|
+
from langchain_agentx.loop.run_context import register_agent_loop, unregister_agent_loop
|
|
25
27
|
from ..exit.reason_codes import (
|
|
26
28
|
TERMINAL_REASON_ABORTED_STREAMING,
|
|
27
29
|
TERMINAL_REASON_ABORTED_TOOLS,
|
|
@@ -84,6 +86,8 @@ class LoopStreamDriver:
|
|
|
84
86
|
self.result.reactive_continue_drains = 0
|
|
85
87
|
current_input = dict(graph_input)
|
|
86
88
|
reactive_continues = 0
|
|
89
|
+
agent_loop = asyncio.get_running_loop()
|
|
90
|
+
register_agent_loop(agent_loop)
|
|
87
91
|
try:
|
|
88
92
|
while True:
|
|
89
93
|
async for event in self._stream_single_drain(
|
|
@@ -113,6 +117,7 @@ class LoopStreamDriver:
|
|
|
113
117
|
(self.result.graph_output or {}).get("transition", {}).get("reason"),
|
|
114
118
|
)
|
|
115
119
|
finally:
|
|
120
|
+
unregister_agent_loop(agent_loop)
|
|
116
121
|
self._log_stream_closeout_summary()
|
|
117
122
|
|
|
118
123
|
async def _stream_single_drain(
|
|
@@ -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),
|