zero-agent-core-multi-agent 0.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.
@@ -0,0 +1,68 @@
1
+ """Handwritten Agent Core 的多 Agent 编排扩展。"""
2
+
3
+ from agent_core_multi_agent.adapters import (
4
+ AgentFunction,
5
+ CallableAgentInvoker,
6
+ RuntimeAgentInvoker,
7
+ RuntimeResultMapper,
8
+ )
9
+ from agent_core_multi_agent.errors import (
10
+ AgentAccessDeniedError,
11
+ AgentInvocationError,
12
+ AgentNotFoundError,
13
+ AgentRegistrationError,
14
+ AgentRoutingError,
15
+ CoordinationBudgetExceededError,
16
+ CoordinationBusyError,
17
+ CoordinationLeaseLostError,
18
+ MultiAgentError,
19
+ )
20
+ from agent_core_multi_agent.protocols import AgentInvoker, AgentRouter, CoordinationStore
21
+ from agent_core_multi_agent.registry import AgentRegistry
22
+ from agent_core_multi_agent.routing import CapabilityRouter, ModelAgentRouter
23
+ from agent_core_multi_agent.storage import WorkflowCoordinationStore
24
+ from agent_core_multi_agent.supervisor import SupervisorAgent
25
+ from agent_core_multi_agent.types import (
26
+ AgentDescriptor,
27
+ AgentResult,
28
+ AgentResultStatus,
29
+ AgentTask,
30
+ CoordinationExecution,
31
+ CoordinationPolicy,
32
+ CoordinationStatus,
33
+ HandoffRequest,
34
+ RoutingDecision,
35
+ )
36
+
37
+ __all__ = [
38
+ "AgentAccessDeniedError",
39
+ "AgentDescriptor",
40
+ "AgentFunction",
41
+ "AgentInvocationError",
42
+ "AgentInvoker",
43
+ "AgentNotFoundError",
44
+ "AgentRegistrationError",
45
+ "AgentRegistry",
46
+ "AgentResult",
47
+ "AgentResultStatus",
48
+ "AgentRouter",
49
+ "AgentRoutingError",
50
+ "AgentTask",
51
+ "CallableAgentInvoker",
52
+ "CapabilityRouter",
53
+ "CoordinationBudgetExceededError",
54
+ "CoordinationBusyError",
55
+ "CoordinationExecution",
56
+ "CoordinationLeaseLostError",
57
+ "CoordinationPolicy",
58
+ "CoordinationStatus",
59
+ "CoordinationStore",
60
+ "HandoffRequest",
61
+ "ModelAgentRouter",
62
+ "MultiAgentError",
63
+ "RoutingDecision",
64
+ "RuntimeAgentInvoker",
65
+ "RuntimeResultMapper",
66
+ "SupervisorAgent",
67
+ "WorkflowCoordinationStore",
68
+ ]
@@ -0,0 +1,139 @@
1
+ """函数和 Core AgentRuntime 的调用适配器。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Awaitable, Callable
7
+
8
+ from agent_core import AgentRuntime, RunContext
9
+
10
+ from agent_core_multi_agent.types import AgentResult, AgentResultStatus, AgentTask
11
+
12
+ AgentFunction = Callable[
13
+ [AgentTask],
14
+ AgentResult | str | Awaitable[AgentResult | str],
15
+ ]
16
+ RuntimeResultMapper = Callable[
17
+ [RunContext, AgentTask],
18
+ AgentResult | Awaitable[AgentResult],
19
+ ]
20
+
21
+
22
+ class CallableAgentInvoker:
23
+ """把一个异步函数包装成 AgentInvoker。"""
24
+
25
+ def __init__(self, agent_id: str, function: AgentFunction) -> None:
26
+ if not agent_id.strip():
27
+ raise ValueError("agent_id 不能为空")
28
+ self._agent_id = agent_id
29
+ self._function = function
30
+
31
+ async def invoke(self, task: AgentTask) -> AgentResult:
32
+ called = self._function(task)
33
+ value = await called if inspect.isawaitable(called) else called
34
+ if isinstance(value, AgentResult):
35
+ return value
36
+ return AgentResult(
37
+ task_id=task.task_id,
38
+ agent_id=self._agent_id,
39
+ status="completed",
40
+ output=str(value),
41
+ )
42
+
43
+
44
+ class RuntimeAgentInvoker:
45
+ """把现有 ``AgentRuntime`` 接入多 Agent 注册表。"""
46
+
47
+ def __init__(
48
+ self,
49
+ agent_id: str,
50
+ runtime: AgentRuntime,
51
+ *,
52
+ result_mapper: RuntimeResultMapper | None = None,
53
+ ) -> None:
54
+ if not agent_id.strip():
55
+ raise ValueError("agent_id 不能为空")
56
+ self._agent_id = agent_id
57
+ self._runtime = runtime
58
+ self._result_mapper = result_mapper
59
+
60
+ async def invoke(self, task: AgentTask) -> AgentResult:
61
+ access = task.access
62
+ context = await self._runtime.run(
63
+ task.thread_id,
64
+ task.input,
65
+ user_id=access.user_id if access else None,
66
+ tenant_id=access.tenant_id if access else None,
67
+ parent_run_id=task.parent_run_id or task.coordination_id,
68
+ idempotency_key=task.idempotency_key,
69
+ metadata={
70
+ **task.metadata,
71
+ "multi_agent_execution_id": task.coordination_id,
72
+ "multi_agent_task_id": task.task_id,
73
+ "multi_agent_context": task.context,
74
+ "multi_agent_lineage": list(task.lineage),
75
+ },
76
+ )
77
+ if (
78
+ task.resume_requested
79
+ and context.status not in {"completed", "cancelled"}
80
+ and context.checkpoint
81
+ ):
82
+ await self._runtime.resume(
83
+ task.thread_id,
84
+ context.run_id,
85
+ access=access,
86
+ )
87
+ context = await self._runtime.wait(
88
+ task.thread_id,
89
+ context.run_id,
90
+ access=access,
91
+ )
92
+ if self._result_mapper is not None:
93
+ mapped = self._result_mapper(context, task)
94
+ return await mapped if inspect.isawaitable(mapped) else mapped
95
+ return self._default_result(context, task)
96
+
97
+ def _default_result(self, context: RunContext, task: AgentTask) -> AgentResult:
98
+ status = _runtime_status(context.status)
99
+ output = ""
100
+ error: str | None = None
101
+ usage: dict[str, float] = {}
102
+ for event in context.events:
103
+ for key, value in event.usage.items():
104
+ if isinstance(value, int | float) and not isinstance(value, bool):
105
+ usage[key] = usage.get(key, 0.0) + float(value)
106
+ if event.event_type == "run_completed":
107
+ output = str(event.payload.get("answer", ""))
108
+ if event.event_type == "run_failed":
109
+ error = event.error or str(event.payload.get("error", "Agent 运行失败"))
110
+ if status == "failed" and not error:
111
+ error = "Agent 运行失败"
112
+ return AgentResult(
113
+ task_id=task.task_id,
114
+ agent_id=self._agent_id,
115
+ status=status,
116
+ output=output,
117
+ run_id=context.run_id,
118
+ error=error,
119
+ usage=usage,
120
+ metadata={"runtime_status": context.status},
121
+ )
122
+
123
+
124
+ def _runtime_status(status: str) -> AgentResultStatus:
125
+ if status == "completed":
126
+ return "completed"
127
+ if status == "failed":
128
+ return "failed"
129
+ if status == "cancelled":
130
+ return "cancelled"
131
+ return "interrupted"
132
+
133
+
134
+ __all__ = [
135
+ "AgentFunction",
136
+ "CallableAgentInvoker",
137
+ "RuntimeAgentInvoker",
138
+ "RuntimeResultMapper",
139
+ ]
@@ -0,0 +1,39 @@
1
+ """多 Agent 编排扩展错误。"""
2
+
3
+ from agent_core import AgentError
4
+
5
+
6
+ class MultiAgentError(AgentError):
7
+ """多 Agent 扩展基础错误。"""
8
+
9
+
10
+ class AgentRegistrationError(MultiAgentError):
11
+ """Agent 注册信息无效或发生名称冲突。"""
12
+
13
+
14
+ class AgentNotFoundError(MultiAgentError):
15
+ """注册表中不存在目标 Agent。"""
16
+
17
+
18
+ class AgentAccessDeniedError(MultiAgentError):
19
+ """当前访问身份无权调用目标 Agent。"""
20
+
21
+
22
+ class AgentRoutingError(MultiAgentError):
23
+ """无法为任务选择唯一且可访问的 Agent。"""
24
+
25
+
26
+ class AgentInvocationError(MultiAgentError):
27
+ """子 Agent 调用失败或返回了无效结果。"""
28
+
29
+
30
+ class CoordinationBudgetExceededError(MultiAgentError):
31
+ """协调实例超过 handoff、调用次数、访问次数或使用量预算。"""
32
+
33
+
34
+ class CoordinationBusyError(MultiAgentError):
35
+ """协调实例已经由其他 Worker 持有。"""
36
+
37
+
38
+ class CoordinationLeaseLostError(MultiAgentError):
39
+ """执行过程中失去协调实例租约。"""
@@ -0,0 +1,49 @@
1
+ """多 Agent 扩展的依赖倒置协议。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
6
+
7
+ from agent_core import AccessContext
8
+
9
+ from agent_core_multi_agent.types import AgentResult, AgentTask, CoordinationExecution, RoutingDecision
10
+
11
+ if TYPE_CHECKING:
12
+ from agent_core_multi_agent.registry import AgentRegistry
13
+
14
+
15
+ @runtime_checkable
16
+ class AgentInvoker(Protocol):
17
+ """任何可由 Supervisor 调用的 Agent 最小接口。"""
18
+
19
+ async def invoke(self, task: AgentTask) -> AgentResult: ...
20
+
21
+
22
+ @runtime_checkable
23
+ class AgentRouter(Protocol):
24
+ """根据任务和当前可访问注册表选择 Agent。"""
25
+
26
+ async def route(self, task: AgentTask, registry: AgentRegistry) -> RoutingDecision: ...
27
+
28
+
29
+ @runtime_checkable
30
+ class CoordinationStore(Protocol):
31
+ """协调实例持久化协议。"""
32
+
33
+ async def save(self, execution: CoordinationExecution) -> None: ...
34
+
35
+ async def load(
36
+ self,
37
+ execution_id: str,
38
+ *,
39
+ access: AccessContext | None = None,
40
+ ) -> CoordinationExecution | None: ...
41
+
42
+ async def list(
43
+ self,
44
+ *,
45
+ access: AccessContext | None = None,
46
+ ) -> list[CoordinationExecution]: ...
47
+
48
+
49
+ __all__ = ["AgentInvoker", "AgentRouter", "CoordinationStore"]
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,133 @@
1
+ """Agent 注册、能力发现、权限过滤和并发限制。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from dataclasses import dataclass
7
+
8
+ from agent_core import AccessContext
9
+
10
+ from agent_core_multi_agent.errors import (
11
+ AgentAccessDeniedError,
12
+ AgentInvocationError,
13
+ AgentNotFoundError,
14
+ AgentRegistrationError,
15
+ )
16
+ from agent_core_multi_agent.protocols import AgentInvoker
17
+ from agent_core_multi_agent.types import AgentDescriptor, AgentResult, AgentTask
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class _RegisteredAgent:
22
+ descriptor: AgentDescriptor
23
+ invoker: AgentInvoker
24
+ semaphore: asyncio.Semaphore | None
25
+
26
+
27
+ class AgentRegistry:
28
+ """保存 Agent 描述和调用器,并在调用前执行权限与并发检查。"""
29
+
30
+ def __init__(self) -> None:
31
+ self._agents: dict[str, _RegisteredAgent] = {}
32
+ self._aliases: dict[str, str] = {}
33
+
34
+ def register(
35
+ self,
36
+ descriptor: AgentDescriptor,
37
+ invoker: AgentInvoker,
38
+ *,
39
+ replace: bool = False,
40
+ ) -> None:
41
+ agent_id = descriptor.agent_id
42
+ collisions = {
43
+ name
44
+ for name in (agent_id, *descriptor.aliases)
45
+ if name in self._aliases and self._aliases[name] != agent_id
46
+ }
47
+ if collisions:
48
+ raise AgentRegistrationError(f"Agent 名称或别名冲突:{', '.join(sorted(collisions))}")
49
+ if agent_id in self._agents and not replace:
50
+ raise AgentRegistrationError(f"Agent 已注册:{agent_id}")
51
+ if replace:
52
+ self.unregister(agent_id)
53
+ semaphore = (
54
+ asyncio.Semaphore(descriptor.max_concurrency) if descriptor.max_concurrency is not None else None
55
+ )
56
+ self._agents[agent_id] = _RegisteredAgent(descriptor, invoker, semaphore)
57
+ self._aliases[agent_id] = agent_id
58
+ for alias in descriptor.aliases:
59
+ self._aliases[alias] = agent_id
60
+
61
+ def unregister(self, agent_id: str) -> bool:
62
+ normalized = self._aliases.get(agent_id, agent_id)
63
+ registered = self._agents.pop(normalized, None)
64
+ if registered is None:
65
+ return False
66
+ for name, owner in tuple(self._aliases.items()):
67
+ if owner == normalized:
68
+ del self._aliases[name]
69
+ return True
70
+
71
+ def get_descriptor(
72
+ self,
73
+ agent_id: str,
74
+ *,
75
+ access: AccessContext | None = None,
76
+ ) -> AgentDescriptor:
77
+ registered = self._resolve(agent_id)
78
+ self._check_access(registered.descriptor, access)
79
+ return registered.descriptor
80
+
81
+ def descriptors(self, *, access: AccessContext | None = None) -> tuple[AgentDescriptor, ...]:
82
+ values = [item.descriptor for item in self._agents.values() if item.descriptor.can_invoke(access)]
83
+ return tuple(sorted(values, key=lambda item: (-item.priority, item.agent_id)))
84
+
85
+ def candidates(
86
+ self,
87
+ capability: str,
88
+ *,
89
+ access: AccessContext | None = None,
90
+ ) -> tuple[AgentDescriptor, ...]:
91
+ normalized = capability.strip()
92
+ return tuple(
93
+ descriptor
94
+ for descriptor in self.descriptors(access=access)
95
+ if normalized in descriptor.capabilities
96
+ )
97
+
98
+ async def invoke(self, agent_id: str, task: AgentTask) -> AgentResult:
99
+ registered = self._resolve(agent_id)
100
+ self._check_access(registered.descriptor, task.access)
101
+
102
+ async def execute() -> AgentResult:
103
+ result = await registered.invoker.invoke(task)
104
+ if result.task_id != task.task_id:
105
+ raise AgentInvocationError(
106
+ f"Agent {registered.descriptor.agent_id} 返回了错误的 task_id:{result.task_id}"
107
+ )
108
+ if result.agent_id != registered.descriptor.agent_id:
109
+ raise AgentInvocationError(
110
+ f"Agent 返回 ID {result.agent_id} 与注册 ID {registered.descriptor.agent_id} 不一致"
111
+ )
112
+ return result
113
+
114
+ if registered.semaphore is None:
115
+ return await execute()
116
+ async with registered.semaphore:
117
+ return await execute()
118
+
119
+ def _resolve(self, name: str) -> _RegisteredAgent:
120
+ agent_id = self._aliases.get(name)
121
+ if agent_id is None:
122
+ available = ", ".join(sorted(self._agents)) or "无"
123
+ raise AgentNotFoundError(f"Agent 不存在:{name};已注册:{available}")
124
+ return self._agents[agent_id]
125
+
126
+ @staticmethod
127
+ def _check_access(descriptor: AgentDescriptor, access: AccessContext | None) -> None:
128
+ if not descriptor.can_invoke(access):
129
+ roles = ", ".join(sorted(descriptor.required_roles))
130
+ raise AgentAccessDeniedError(f"调用 Agent {descriptor.agent_id} 需要角色:{roles}")
131
+
132
+
133
+ __all__ = ["AgentRegistry"]
@@ -0,0 +1,124 @@
1
+ """确定性能力路由和可选模型路由。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from agent_core import ModelAdapter, StructuredOutputSpec, chat_structured, system_message, user_message
9
+
10
+ from agent_core_multi_agent.errors import AgentRoutingError
11
+ from agent_core_multi_agent.registry import AgentRegistry
12
+ from agent_core_multi_agent.types import AgentTask, RoutingDecision
13
+
14
+
15
+ class CapabilityRouter:
16
+ """优先显式目标,其次能力匹配,最后使用可选默认 Agent。"""
17
+
18
+ def __init__(self, default_agent_id: str | None = None) -> None:
19
+ self._default_agent_id = default_agent_id
20
+
21
+ async def route(self, task: AgentTask, registry: AgentRegistry) -> RoutingDecision:
22
+ if task.target_agent_id:
23
+ descriptor = registry.get_descriptor(task.target_agent_id, access=task.access)
24
+ if (
25
+ task.required_capability
26
+ and task.required_capability not in descriptor.capabilities
27
+ ):
28
+ raise AgentRoutingError(
29
+ f"Agent {descriptor.agent_id} 不提供能力:{task.required_capability}"
30
+ )
31
+ return RoutingDecision(descriptor.agent_id, "任务显式指定目标 Agent")
32
+
33
+ if task.required_capability:
34
+ candidates = registry.candidates(task.required_capability, access=task.access)
35
+ if not candidates:
36
+ raise AgentRoutingError(f"没有可访问的 Agent 提供能力:{task.required_capability}")
37
+ selected = candidates[0]
38
+ return RoutingDecision(
39
+ selected.agent_id,
40
+ f"按能力 {task.required_capability} 和优先级选择",
41
+ )
42
+
43
+ if self._default_agent_id:
44
+ descriptor = registry.get_descriptor(self._default_agent_id, access=task.access)
45
+ return RoutingDecision(descriptor.agent_id, "使用默认 Agent")
46
+
47
+ candidates = registry.descriptors(access=task.access)
48
+ if len(candidates) == 1:
49
+ return RoutingDecision(candidates[0].agent_id, "只有一个可访问的 Agent")
50
+ raise AgentRoutingError("任务没有指定目标或能力,且无法唯一选择 Agent")
51
+
52
+
53
+ class ModelAgentRouter:
54
+ """让模型在权限过滤后的候选集合中选择 Agent。"""
55
+
56
+ def __init__(
57
+ self,
58
+ model: ModelAdapter,
59
+ *,
60
+ fallback: CapabilityRouter | None = None,
61
+ max_retries: int = 1,
62
+ ) -> None:
63
+ self._model = model
64
+ self._fallback = fallback or CapabilityRouter()
65
+ self._max_retries = max_retries
66
+
67
+ async def route(self, task: AgentTask, registry: AgentRegistry) -> RoutingDecision:
68
+ if task.target_agent_id or task.required_capability:
69
+ return await self._fallback.route(task, registry)
70
+
71
+ candidates = registry.descriptors(access=task.access)
72
+ if not candidates:
73
+ raise AgentRoutingError("没有可访问的 Agent")
74
+ if len(candidates) == 1:
75
+ return RoutingDecision(candidates[0].agent_id, "只有一个可访问的 Agent")
76
+
77
+ agent_ids = [item.agent_id for item in candidates]
78
+ candidate_payload = [
79
+ {
80
+ "agent_id": item.agent_id,
81
+ "description": item.description,
82
+ "capabilities": sorted(item.capabilities),
83
+ }
84
+ for item in candidates
85
+ ]
86
+ spec = StructuredOutputSpec[RoutingDecision](
87
+ name="agent_routing_decision",
88
+ schema={
89
+ "type": "object",
90
+ "properties": {
91
+ "agent_id": {"type": "string", "enum": agent_ids},
92
+ "reason": {"type": "string", "minLength": 1},
93
+ },
94
+ "required": ["agent_id", "reason"],
95
+ "additionalProperties": False,
96
+ },
97
+ decoder=self._decode,
98
+ max_retries=self._max_retries,
99
+ )
100
+ result = await chat_structured(
101
+ self._model,
102
+ [
103
+ system_message(
104
+ "你是多 Agent 路由器。只能从候选 Agent 中选择一个最适合当前任务的 Agent。"
105
+ ),
106
+ user_message(
107
+ "任务:"
108
+ f"{task.input}\n上下文:{json.dumps(task.context, ensure_ascii=False, default=str)}"
109
+ f"\n候选:{json.dumps(candidate_payload, ensure_ascii=False)}"
110
+ ),
111
+ ],
112
+ spec,
113
+ temperature=0,
114
+ )
115
+ return result.value
116
+
117
+ @staticmethod
118
+ def _decode(value: Any) -> RoutingDecision:
119
+ if not isinstance(value, dict):
120
+ raise TypeError("路由结果必须是对象")
121
+ return RoutingDecision(agent_id=str(value["agent_id"]), reason=str(value["reason"]))
122
+
123
+
124
+ __all__ = ["CapabilityRouter", "ModelAgentRouter"]
@@ -0,0 +1,110 @@
1
+ """复用 Core Workflow Store 的协调实例存储适配器。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from agent_core import (
8
+ AccessContext,
9
+ MemoryWorkflowExecutionStore,
10
+ WorkflowExecution,
11
+ WorkflowExecutionStore,
12
+ )
13
+
14
+ from agent_core_multi_agent.types import CoordinationExecution
15
+
16
+
17
+ class WorkflowCoordinationStore:
18
+ """把协调快照保存到 Core 的 Memory/SQLite/PostgreSQL Workflow Store。"""
19
+
20
+ def __init__(
21
+ self,
22
+ store: WorkflowExecutionStore | None = None,
23
+ *,
24
+ definition_id: str = "multi-agent-supervisor",
25
+ require_access: bool = False,
26
+ ) -> None:
27
+ self._store = store or MemoryWorkflowExecutionStore()
28
+ self._definition_id = definition_id
29
+ self._require_access = require_access
30
+
31
+ async def save(self, execution: CoordinationExecution) -> None:
32
+ access = execution.access
33
+ if self._require_access and access is None:
34
+ raise PermissionError("该 CoordinationStore 要求提供 AccessContext")
35
+ payload = execution.to_dict()
36
+ try:
37
+ json.dumps(payload, ensure_ascii=False)
38
+ except (TypeError, ValueError) as exc:
39
+ raise TypeError("协调实例包含不可 JSON 序列化的数据") from exc
40
+ record = WorkflowExecution(
41
+ execution_id=execution.execution_id,
42
+ definition_id=self._definition_id,
43
+ execution_type="multi_agent",
44
+ user_id=access.user_id if access else None,
45
+ tenant_id=access.tenant_id if access else None,
46
+ status=execution.status,
47
+ current_step=execution.current_agent_id,
48
+ steps_completed=len(execution.results),
49
+ total_steps=execution.agent_calls,
50
+ input_data=execution.root_task.to_dict(),
51
+ result_data=payload,
52
+ events=[event.to_dict() for event in execution.events],
53
+ version=execution.version,
54
+ created_at=execution.created_at,
55
+ updated_at=execution.updated_at,
56
+ )
57
+ await self._store.save(record)
58
+ execution.version = record.version
59
+ execution.updated_at = record.updated_at
60
+
61
+ async def load(
62
+ self,
63
+ execution_id: str,
64
+ *,
65
+ access: AccessContext | None = None,
66
+ ) -> CoordinationExecution | None:
67
+ record = await self._store.load(execution_id)
68
+ if record is None or record.execution_type != "multi_agent":
69
+ return None
70
+ self._check_access(record, access)
71
+ execution = CoordinationExecution.from_dict(record.result_data)
72
+ execution.version = record.version
73
+ execution.updated_at = record.updated_at
74
+ return execution
75
+
76
+ async def list(
77
+ self,
78
+ *,
79
+ access: AccessContext | None = None,
80
+ ) -> list[CoordinationExecution]:
81
+ if self._require_access and access is None:
82
+ raise PermissionError("该 CoordinationStore 要求提供 AccessContext")
83
+ tenant_id = access.tenant_id if access else None
84
+ records = await self._store.list(self._definition_id, tenant_id)
85
+ executions: list[CoordinationExecution] = []
86
+ for record in records:
87
+ if record.execution_type != "multi_agent":
88
+ continue
89
+ if access is not None and not access.can_access(record.user_id, record.tenant_id):
90
+ continue
91
+ execution = CoordinationExecution.from_dict(record.result_data)
92
+ execution.version = record.version
93
+ execution.updated_at = record.updated_at
94
+ executions.append(execution)
95
+ return executions
96
+
97
+ def _check_access(
98
+ self,
99
+ record: WorkflowExecution,
100
+ access: AccessContext | None,
101
+ ) -> None:
102
+ if self._require_access and access is None:
103
+ raise PermissionError("该 CoordinationStore 要求提供 AccessContext")
104
+ if self._require_access and (record.user_id is None or record.tenant_id is None):
105
+ raise PermissionError("该协调实例尚未绑定用户和租户")
106
+ if access is not None and not access.can_access(record.user_id, record.tenant_id):
107
+ raise PermissionError("无权访问该协调实例")
108
+
109
+
110
+ __all__ = ["WorkflowCoordinationStore"]