Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plugins/agent_compose_strategy/client/agent_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,15 @@ def cleanup_policy_keeps_sandbox(value: str) -> bool:
return cleanup_policy_to_proto(value) == "RUN_SANDBOX_CLEANUP_POLICY_KEEP_RUNNING"


def cleanup_policy_reuses_sandbox(value: str) -> bool:
"""Whether a later run may reuse this run's sandbox.

A stopped sandbox is still resumable when its id is supplied to
agent-compose. Only remove-on-completion makes the id unusable.
"""
return cleanup_policy_to_proto(value) != "RUN_SANDBOX_CLEANUP_POLICY_REMOVE_ON_COMPLETION"


def parse_agent_selection(value: str) -> tuple[str, str]:
try:
payload = json.loads(value)
Expand Down
8 changes: 4 additions & 4 deletions plugins/agent_compose_strategy/strategies/dynamic_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
AgentComposeClient,
AgentComposeConfig,
AgentComposeError,
cleanup_policy_keeps_sandbox,
cleanup_policy_reuses_sandbox,
remember_agent_compose_sandbox_id,
resolve_agent_compose_sandbox_id,
resolve_agent_reference,
Expand Down Expand Up @@ -45,9 +45,9 @@ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage, N
)
)
project_id, agent_name = resolve_agent_reference(client, params.agent)
keep_sandbox = cleanup_policy_keeps_sandbox(params.cleanup_policy)
reuse_sandbox = cleanup_policy_reuses_sandbox(params.cleanup_policy)
sandbox_id = ""
if keep_sandbox:
if reuse_sandbox:
sandbox_id = resolve_agent_compose_sandbox_id(
explicit_sandbox_id=None,
dify_session=self.session,
Expand Down Expand Up @@ -94,7 +94,7 @@ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage, N
)
raise

if keep_sandbox:
if reuse_sandbox:
remember_agent_compose_sandbox_id(
explicit_sandbox_id=None,
dify_session=self.session,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_agent_strategy_does_not_expose_manual_sandbox_id() -> None:


@responses.activate
def test_agent_strategy_stop_mode_skips_storage_and_emits_distinct_outputs() -> None:
def test_agent_strategy_stop_mode_remembers_sandbox_and_emits_distinct_outputs() -> None:
base_url = "http://agent-compose.test"
responses.post(
base_url + RUN_AGENT_PROCEDURE,
Expand All @@ -60,7 +60,7 @@ def test_agent_strategy_stop_mode_skips_storage_and_emits_distinct_outputs() ->
)
session = Session.empty_session()
session.conversation_id = "conversation-1"
session.storage = FailingStorage(ValueError("storage must not be used"))
session.storage = FakeStorage()
strategy = DynamicWorkflowAgentStrategy(
runtime=AgentRuntime(user_id="user-1"),
session=session,
Expand Down Expand Up @@ -93,6 +93,7 @@ def test_agent_strategy_stop_mode_skips_storage_and_emits_distinct_outputs() ->
"warnings": [],
}
assert b'"sandboxId"' not in responses.calls[0].request.body
assert list(session.storage.values.values()) == [b"sandbox-1"]


def test_agent_strategy_accepts_connection_settings() -> None:
Expand Down Expand Up @@ -684,7 +685,7 @@ def test_run_agent_uses_tool_provider_credentials() -> None:
)
session = Session.empty_session()
session.conversation_id = "conversation-1"
session.storage = FailingStorage(ValueError("storage must not be used"))
session.storage = FakeStorage()
tool = RunAgentTool(
runtime=ToolRuntime(
credentials={"agent_compose_url": base_url, "agent_compose_token": "token"},
Expand Down
9 changes: 9 additions & 0 deletions plugins/agent_compose_workflow/client/agent_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,15 @@ def cleanup_policy_keeps_sandbox(value: str) -> bool:
return cleanup_policy_to_proto(value) == "RUN_SANDBOX_CLEANUP_POLICY_KEEP_RUNNING"


def cleanup_policy_reuses_sandbox(value: str) -> bool:
"""Whether a later run may reuse this run's sandbox.

A stopped sandbox is still resumable when its id is supplied to
agent-compose. Only remove-on-completion makes the id unusable.
"""
return cleanup_policy_to_proto(value) != "RUN_SANDBOX_CLEANUP_POLICY_REMOVE_ON_COMPLETION"


def parse_agent_selection(value: str) -> tuple[str, str]:
try:
payload = json.loads(value)
Expand Down
6 changes: 3 additions & 3 deletions plugins/agent_compose_workflow/docs/architecture.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ Strategy 目前使用文本填写 Agent 名称,这是因为 Dify 的 Agent Str

沙箱清理策略有三种:

- `stop_on_completion`:任务完成后停止沙箱。当前 Tool 与 Strategy 均默认使用该模式,适合大多数一次性任务
- `stop_on_completion`:任务完成后停止沙箱。停止后仍会保存会话到 sandbox id,后续同一 Dify 会话可恢复该 sandbox;适合不希望 runtime 长期运行、但需要多轮对话的任务
- `keep_running`:任务完成后保持沙箱运行,适合需要跨轮保留文件、进程或运行状态的会话。
- `remove_on_completion`:任务完成后删除沙箱,适合不需要保留环境、希望及时释放资源的任务。

当使用 `keep_running` 时,插件会根据 Dify 的 conversation id、project id 和 Agent 名称保存 agent-compose 返回的 sandbox id。后续在同一 Dify 会话中再次调用同一 Agent,便可以自动复用原沙箱;同一会话中的不同 Agent 会使用不同沙箱,避免运行环境串用。
当使用 `keep_running` 或 `stop_on_completion` 时,插件会根据 Dify 的 conversation id、project id 和 Agent 名称保存 agent-compose 返回的 sandbox id。后续在同一 Dify 会话中再次调用同一 Agent,便可以自动复用原沙箱;同一会话中的不同 Agent 会使用不同沙箱,避免运行环境串用。agent-compose 部署需要保留 stopped runtime 的状态,才能恢复 provider 的原生会话

非会话型执行如果没有 conversation id,则每次都会创建新沙箱。`stop_on_completion` 和 `remove_on_completion` 也不会读写这份会话存储。插件有意不暴露手工填写 `sandbox_id` 的参数,以减少错误复用运行环境的风险。
非会话型执行如果没有 conversation id,则每次都会创建新沙箱。`remove_on_completion` 不会读写这份会话存储;插件有意不暴露手工填写 `sandbox_id` 的参数,以减少错误复用运行环境的风险。

简单来说:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ def test_run_agent_uses_tool_provider_credentials() -> None:
)
session = Session.empty_session()
session.conversation_id = "conversation-1"
session.storage = FailingStorage(ValueError("storage must not be used"))
session.storage = FakeStorage()
tool = RunAgentTool(
runtime=ToolRuntime(
credentials={"agent_compose_url": base_url, "agent_compose_token": "token"},
Expand Down
8 changes: 4 additions & 4 deletions plugins/agent_compose_workflow/tools/run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
AgentComposeClient,
AgentComposeConfig,
AgentComposeError,
cleanup_policy_keeps_sandbox,
cleanup_policy_reuses_sandbox,
parse_agent_selection,
remember_agent_compose_sandbox_id,
resolve_agent_compose_sandbox_id,
Expand All @@ -29,9 +29,9 @@ def _invoke(
if not query:
raise AgentComposeError("query is required")
cleanup_policy = str(tool_parameters.get("cleanup_policy") or "stop_on_completion")
keep_sandbox = cleanup_policy_keeps_sandbox(cleanup_policy)
reuse_sandbox = cleanup_policy_reuses_sandbox(cleanup_policy)
sandbox_id = ""
if keep_sandbox:
if reuse_sandbox:
sandbox_id = resolve_agent_compose_sandbox_id(
explicit_sandbox_id=None,
dify_session=self.session,
Expand Down Expand Up @@ -80,7 +80,7 @@ def _invoke(
)
raise

if keep_sandbox:
if reuse_sandbox:
remember_agent_compose_sandbox_id(
explicit_sandbox_id=None,
dify_session=self.session,
Expand Down
Loading