agent-relay 3.2.2 → 3.2.4

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.
Files changed (246) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +1358 -941
  6. package/dist/src/cli/commands/agent-management.d.ts +2 -2
  7. package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
  8. package/dist/src/cli/commands/agent-management.js +41 -240
  9. package/dist/src/cli/commands/agent-management.js.map +1 -1
  10. package/dist/src/cli/commands/messaging.d.ts +1 -1
  11. package/dist/src/cli/commands/messaging.d.ts.map +1 -1
  12. package/dist/src/cli/commands/messaging.js +14 -5
  13. package/dist/src/cli/commands/messaging.js.map +1 -1
  14. package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
  15. package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
  16. package/dist/src/cli/lib/agent-management-listing.js +27 -2
  17. package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
  18. package/package.json +11 -10
  19. package/packages/acp-bridge/package.json +2 -2
  20. package/packages/config/package.json +1 -1
  21. package/packages/hooks/package.json +4 -4
  22. package/packages/memory/package.json +2 -2
  23. package/packages/openclaw/package.json +2 -2
  24. package/packages/policy/package.json +2 -2
  25. package/packages/sdk/ADAPTER_REVIEW.md +109 -0
  26. package/packages/sdk/dist/client.d.ts +66 -0
  27. package/packages/sdk/dist/client.d.ts.map +1 -1
  28. package/packages/sdk/dist/client.js +230 -0
  29. package/packages/sdk/dist/client.js.map +1 -1
  30. package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
  31. package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
  32. package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
  33. package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
  34. package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
  35. package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
  36. package/packages/sdk/dist/communicate/a2a-server.js +220 -0
  37. package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
  38. package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
  39. package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
  40. package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
  41. package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
  42. package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
  43. package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
  44. package/packages/sdk/dist/communicate/a2a-types.js +209 -0
  45. package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
  46. package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
  47. package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
  48. package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
  49. package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
  50. package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
  51. package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
  52. package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
  53. package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
  54. package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
  55. package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
  56. package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
  57. package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
  58. package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
  59. package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
  60. package/packages/sdk/dist/communicate/adapters/index.js +7 -0
  61. package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
  62. package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
  63. package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
  64. package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
  65. package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
  66. package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
  67. package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
  68. package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
  69. package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
  70. package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
  71. package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
  72. package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
  73. package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
  74. package/packages/sdk/dist/communicate/core.d.ts +58 -0
  75. package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
  76. package/packages/sdk/dist/communicate/core.js +128 -0
  77. package/packages/sdk/dist/communicate/core.js.map +1 -0
  78. package/packages/sdk/dist/communicate/index.d.ts +4 -0
  79. package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
  80. package/packages/sdk/dist/communicate/index.js +4 -0
  81. package/packages/sdk/dist/communicate/index.js.map +1 -0
  82. package/packages/sdk/dist/communicate/transport.d.ts +36 -0
  83. package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
  84. package/packages/sdk/dist/communicate/transport.js +371 -0
  85. package/packages/sdk/dist/communicate/transport.js.map +1 -0
  86. package/packages/sdk/dist/communicate/types.d.ts +58 -0
  87. package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
  88. package/packages/sdk/dist/communicate/types.js +66 -0
  89. package/packages/sdk/dist/communicate/types.js.map +1 -0
  90. package/packages/sdk/dist/workflows/builder.d.ts +35 -5
  91. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  92. package/packages/sdk/dist/workflows/builder.js +81 -7
  93. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  94. package/packages/sdk/dist/workflows/cli.js +14 -1
  95. package/packages/sdk/dist/workflows/cli.js.map +1 -1
  96. package/packages/sdk/dist/workflows/runner.d.ts +10 -2
  97. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  98. package/packages/sdk/dist/workflows/runner.js +95 -1
  99. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  100. package/packages/sdk/dist/workflows/types.d.ts +11 -0
  101. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  102. package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
  103. package/packages/sdk/examples/communicate/pi_example.ts +8 -0
  104. package/packages/sdk/package.json +48 -2
  105. package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
  106. package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
  107. package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
  108. package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
  109. package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
  110. package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
  111. package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
  112. package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
  113. package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
  114. package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
  115. package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
  116. package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
  117. package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
  118. package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
  119. package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
  120. package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
  121. package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
  122. package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
  123. package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
  124. package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
  125. package/packages/sdk/src/client.ts +301 -0
  126. package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
  127. package/packages/sdk/src/communicate/a2a-server.ts +277 -0
  128. package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
  129. package/packages/sdk/src/communicate/a2a-types.ts +338 -0
  130. package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
  131. package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
  132. package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
  133. package/packages/sdk/src/communicate/adapters/index.ts +6 -0
  134. package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
  135. package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
  136. package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
  137. package/packages/sdk/src/communicate/core.ts +157 -0
  138. package/packages/sdk/src/communicate/index.ts +3 -0
  139. package/packages/sdk/src/communicate/transport.ts +489 -0
  140. package/packages/sdk/src/communicate/types.ts +106 -0
  141. package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
  142. package/packages/sdk/src/workflows/builder.ts +97 -9
  143. package/packages/sdk/src/workflows/cli.ts +16 -1
  144. package/packages/sdk/src/workflows/runner.ts +110 -1
  145. package/packages/sdk/src/workflows/types.ts +14 -0
  146. package/packages/sdk/tsconfig.build.json +1 -7
  147. package/packages/sdk/tsconfig.json +1 -7
  148. package/packages/sdk-py/README.md +67 -25
  149. package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
  150. package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
  151. package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
  152. package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
  153. package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
  154. package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
  155. package/packages/sdk-py/pyproject.toml +12 -1
  156. package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
  157. package/packages/sdk-py/src/agent_relay/builder.py +65 -26
  158. package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
  159. package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
  160. package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
  161. package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
  162. package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
  163. package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
  164. package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
  165. package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
  166. package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
  167. package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
  168. package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
  169. package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
  170. package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
  171. package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
  172. package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
  173. package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
  174. package/packages/sdk-py/src/agent_relay/types.py +2 -1
  175. package/packages/sdk-py/tests/communicate/__init__.py +0 -0
  176. package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
  177. package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
  178. package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
  179. package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
  180. package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
  181. package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
  182. package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
  183. package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
  184. package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
  185. package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
  186. package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
  187. package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
  188. package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
  189. package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
  190. package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
  191. package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
  192. package/packages/sdk-py/tests/communicate/conftest.py +555 -0
  193. package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
  194. package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
  195. package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
  196. package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
  197. package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
  198. package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
  199. package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
  200. package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
  201. package/packages/sdk-py/tests/communicate/test_core.py +331 -0
  202. package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
  203. package/packages/sdk-py/tests/communicate/test_types.py +285 -0
  204. package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
  205. package/packages/telemetry/package.json +1 -1
  206. package/packages/trajectory/package.json +2 -2
  207. package/packages/user-directory/package.json +2 -2
  208. package/packages/utils/package.json +2 -2
  209. package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
  210. package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
  211. package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
  212. package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
  213. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
  214. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
  215. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
  216. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
  217. package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
  218. package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
  219. package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
  220. package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
  221. package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
  222. package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
  223. package/packages/sdk/dist/__tests__/facade.test.js +0 -305
  224. package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
  225. package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
  226. package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
  227. package/packages/sdk/dist/__tests__/integration.test.js +0 -205
  228. package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
  229. package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
  230. package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
  231. package/packages/sdk/dist/__tests__/pty.test.js +0 -20
  232. package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
  233. package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
  234. package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
  235. package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
  236. package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
  237. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
  238. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
  239. package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
  240. package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
  241. package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
  242. package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
  243. package/packages/sdk/dist/__tests__/unit.test.js +0 -357
  244. package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
  245. package/packages/sdk-py/agent_relay/__init__.py +0 -21
  246. package/packages/sdk-py/agent_relay/models.py +0 -398
@@ -1,20 +1,35 @@
1
1
  # Agent Relay Python SDK
2
2
 
3
- Python SDK for real-time agent-to-agent communication. Spawn AI agents, send messages, and coordinate multi-agent workflows with a simple async API.
3
+ Python SDK for two workflows:
4
+
5
+ - `Orchestrate` mode spawns and manages AI agents from Python.
6
+ - `Communicate` mode puts an existing agent framework on Relaycast with `Relay` + `on_relay()`.
4
7
 
5
8
  ## Installation
6
9
 
10
+ ### Orchestrate
11
+
7
12
  ```bash
8
13
  pip install agent-relay-sdk
9
14
  ```
10
15
 
11
- The SDK automatically downloads the broker binary on first use.
16
+ ### Communicate
17
+
18
+ ```bash
19
+ pip install "agent-relay-sdk[communicate]"
20
+ ```
21
+
22
+ The SDK automatically downloads the broker binary on first use. Communicate mode also needs the framework package you want to wrap, such as `claude-agent-sdk`, `google-adk`, `agno`, `swarms`, or `crewai`.
12
23
 
13
24
  ## Requirements
14
25
 
15
26
  - Python 3.10+
16
27
 
17
- ## Quick Start
28
+ ## Choose a Mode
29
+
30
+ ### Orchestrate
31
+
32
+ Use `AgentRelay` when you want Python to spawn agents, wait for readiness, route messages, and shut everything down.
18
33
 
19
34
  ```python
20
35
  import asyncio
@@ -22,45 +37,56 @@ from agent_relay import AgentRelay, Models
22
37
 
23
38
  async def main():
24
39
  relay = AgentRelay(channels=["dev"])
25
-
26
- # Event hooks
27
40
  relay.on_message_received = lambda msg: print(f"[{msg.from_name}]: {msg.text}")
28
- relay.on_agent_ready = lambda agent: print(f" {agent.name} ready")
29
- relay.on_agent_exited = lambda agent: print(f" {agent.name} exited")
30
41
 
31
- # Spawn agents
32
42
  await relay.claude.spawn(
33
43
  name="Reviewer",
34
44
  model=Models.Claude.OPUS,
35
45
  channels=["dev"],
36
46
  task="Review the PR and suggest improvements",
37
47
  )
38
-
39
48
  await relay.codex.spawn(
40
49
  name="Builder",
41
50
  model=Models.Codex.GPT_5_3_CODEX,
42
51
  channels=["dev"],
43
- task="Implement the suggested improvements",
52
+ task="Implement the suggestions",
44
53
  )
45
54
 
46
- # Wait for both agents to be ready
47
55
  await asyncio.gather(
48
56
  relay.wait_for_agent_ready("Reviewer"),
49
57
  relay.wait_for_agent_ready("Builder"),
50
58
  )
51
-
52
- # Let agents collaborate, then shut down
53
- await asyncio.sleep(600)
54
59
  await relay.shutdown()
55
60
 
56
61
  asyncio.run(main())
57
62
  ```
58
63
 
64
+ ### Communicate
65
+
66
+ Use `Relay` + `on_relay()` when your framework already owns the runtime and you only want Relaycast messaging.
67
+
68
+ ```python
69
+ relay = Relay("Researcher")
70
+ agent = FrameworkAgent(...)
71
+ agent = on_relay(agent, relay)
72
+ ```
73
+
74
+ Supported Python adapters:
75
+
76
+ - OpenAI Agents
77
+ - Claude Agent SDK
78
+ - Google ADK
79
+ - Agno
80
+ - Swarms
81
+ - CrewAI
82
+
83
+ The wrapped agent gets Relay tools for direct messages, channel posts, inbox reads, and agent discovery. Framework-specific receive hooks are added automatically.
84
+
59
85
  ## API
60
86
 
61
87
  ### AgentRelay
62
88
 
63
- The main entry point. Pass `channels` to subscribe to message channels.
89
+ The main Orchestrate entry point. Pass `channels` to subscribe to message channels.
64
90
 
65
91
  ```python
66
92
  relay = AgentRelay(channels=["dev", "planning"])
@@ -78,7 +104,6 @@ await relay.gemini.spawn(name="Agent3", model=Models.Gemini.GEMINI_2_5_PRO, chan
78
104
  worker = await relay.claude.spawn(
79
105
  name="HookedWorker",
80
106
  channels=["dev"],
81
- # Lifecycle hooks can be sync or async callables.
82
107
  on_start=lambda ctx: print(f"spawning {ctx['name']}"),
83
108
  on_success=lambda ctx: print(f"spawned {ctx['name']} ({ctx['runtime']})"),
84
109
  on_error=lambda ctx: print(f"failed to spawn {ctx['name']}: {ctx['error']}"),
@@ -91,22 +116,39 @@ await worker.release(
91
116
  )
92
117
  ```
93
118
 
94
- ### Sending Messages
119
+ ### Relay
120
+
121
+ The Communicate-mode client. Configure it directly or via `RELAY_WORKSPACE`, `RELAY_API_KEY`, and `RELAY_BASE_URL`.
95
122
 
96
123
  ```python
97
- human = relay.system()
98
- await human.send_message(to="Agent1", text="Please start the analysis")
124
+ from agent_relay import Relay
125
+
126
+ relay = Relay("Researcher")
127
+ await relay.send("Lead", "Status update")
128
+ await relay.post("docs", "Wave 5.1 complete")
129
+ messages = await relay.inbox()
130
+ ```
131
+
132
+ ### `on_relay()`
133
+
134
+ Wrap a framework-owned agent or options object and keep the runtime you already use.
135
+
136
+ ```python
137
+ from agent_relay import Relay, on_relay
138
+
139
+ relay = Relay("Researcher")
140
+ wrapped = on_relay(framework_agent_or_options, relay)
99
141
  ```
100
142
 
101
143
  ### Event Hooks
102
144
 
103
145
  ```python
104
- relay.on_message_received = lambda msg: ... # New message
105
- relay.on_agent_ready = lambda agent: ... # Agent connected
106
- relay.on_agent_exited = lambda agent: ... # Agent exited
107
- relay.on_agent_spawned = lambda agent: ... # Agent spawned
108
- relay.on_worker_output = lambda data: ... # Agent output
109
- relay.on_agent_idle = lambda agent: ... # Agent idle
146
+ relay.on_message_received = lambda msg: ... # New message
147
+ relay.on_agent_ready = lambda agent: ... # Agent connected
148
+ relay.on_agent_exited = lambda agent: ... # Agent exited
149
+ relay.on_agent_spawned = lambda agent: ... # Agent spawned
150
+ relay.on_worker_output = lambda data: ... # Agent output
151
+ relay.on_agent_idle = lambda agent: ... # Agent idle
110
152
  ```
111
153
 
112
154
  ### Models
@@ -0,0 +1,8 @@
1
+ from agent_relay import Relay, on_relay
2
+ from agno.agent import Agent
3
+ from agno.models.openai import OpenAIChat
4
+
5
+ relay = Relay("AgnoWorker")
6
+ agent = Agent(model=OpenAIChat(id="gpt-5-mini"), instructions="Use relay tools when needed.")
7
+ agent = on_relay(agent, relay)
8
+ print(len(agent.tools))
@@ -0,0 +1,6 @@
1
+ from agent_relay import Relay, on_relay
2
+ from claude_agent_sdk import ClaudeAgentOptions
3
+
4
+ relay = Relay("ClaudeWorker")
5
+ options = on_relay("ClaudeWorker", ClaudeAgentOptions(), relay)
6
+ print(options.mcp_servers)
@@ -0,0 +1,7 @@
1
+ from agent_relay import Relay, on_relay
2
+ from crewai import Agent
3
+
4
+ relay = Relay("CrewWorker")
5
+ agent = Agent(role="Relay worker", goal="Coordinate with teammates", backstory="You check relay messages before acting.", llm="gpt-4o-mini")
6
+ agent = on_relay(agent, relay)
7
+ print(agent.backstory)
@@ -0,0 +1,7 @@
1
+ from agent_relay import Relay, on_relay
2
+ from google.adk.agents import Agent
3
+
4
+ relay = Relay("GoogleWorker")
5
+ agent = Agent(name="GoogleWorker", model="gemini-2.0-flash", instruction="Use relay tools when needed.")
6
+ agent = on_relay(agent, relay)
7
+ print([tool.__name__ for tool in agent.tools])
@@ -0,0 +1,8 @@
1
+ from agent_relay import Relay
2
+ from agent_relay.communicate.adapters.openai_agents import on_relay
3
+ from openai_agents import Agent
4
+
5
+ relay = Relay("OpenAIWorker")
6
+ agent = Agent(name="OpenAIWorker", instructions="Check relay_inbox before acting.")
7
+ agent = on_relay(agent, relay)
8
+ print(len(agent.tools))
@@ -0,0 +1,7 @@
1
+ from agent_relay import Relay, on_relay
2
+ from swarms import Agent
3
+
4
+ relay = Relay("SwarmsWorker")
5
+ agent = Agent(agent_name="SwarmsWorker", system_prompt="Use relay tools when needed.", model_name="gpt-4o-mini", max_loops=1)
6
+ agent = on_relay(agent, relay)
7
+ print(agent.run("Check relay_inbox, then say hello to the team."))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agent-relay-sdk"
7
- version = "3.2.2"
7
+ version = "3.2.4"
8
8
  description = "Python SDK for Agent Relay workflows"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -14,9 +14,20 @@ dependencies = [
14
14
  ]
15
15
 
16
16
  [project.optional-dependencies]
17
+ communicate = [
18
+ "aiohttp>=3.9",
19
+ ]
20
+ openai-agents = ["openai-agents>=0.1"]
21
+ claude-sdk = ["claude-agent-sdk>=0.1"]
22
+ google-adk = ["google-adk>=0.1"]
23
+ agno = ["agno>=0.1"]
24
+ swarms = ["swarms>=0.1"]
25
+ crewai = ["crewai>=0.1"]
17
26
  dev = [
27
+ "aiohttp>=3.9",
18
28
  "pytest>=8.0",
19
29
  "pytest-asyncio>=0.23",
30
+ "pytest-cov>=5.0",
20
31
  ]
21
32
 
22
33
  [tool.hatch.build.targets.wheel]
@@ -3,6 +3,13 @@
3
3
  # ── Primary API: Direct spawn/message (matches TypeScript SDK) ────────────────
4
4
 
5
5
  from .relay import AgentRelay, Agent, AgentSpawner, HumanHandle, Message, SpawnOptions
6
+ _has_communicate = False
7
+ try:
8
+ from .communicate import Relay, RelayConfig, on_relay
9
+ _has_communicate = True
10
+ except ImportError:
11
+ # communicate extras not installed (pip install agent-relay-sdk[communicate])
12
+ pass
6
13
  from .models import Models
7
14
  from .client import AgentRelayClient, AgentRelayProtocolError, AgentRelayProcessError
8
15
  from .protocol import (
@@ -76,6 +83,7 @@ __all__ = [
76
83
  "HumanHandle",
77
84
  "Message",
78
85
  "SpawnOptions",
86
+ *(["Relay", "RelayConfig", "on_relay"] if _has_communicate else []),
79
87
  "Models",
80
88
  "AgentRelayClient",
81
89
  "AgentRelayProtocolError",
@@ -243,36 +243,71 @@ class WorkflowBuilder:
243
243
  self,
244
244
  name: str,
245
245
  *,
246
- agent: str,
247
- task: str,
246
+ type: str | None = None,
247
+ # Agent step fields
248
+ agent: str | None = None,
249
+ task: str | None = None,
250
+ # Common fields
248
251
  depends_on: list[str] | None = None,
249
252
  verification: VerificationCheck | None = None,
250
253
  timeout_ms: int | None = None,
251
254
  retries: int | None = None,
255
+ # Deterministic step fields
256
+ command: str | None = None,
257
+ capture_output: bool | None = None,
258
+ fail_on_error: bool | None = None,
259
+ # Worktree step fields
260
+ branch: str | None = None,
261
+ base_branch: str | None = None,
262
+ path: str | None = None,
263
+ create_branch: bool | None = None,
252
264
  ) -> WorkflowBuilder:
253
265
  """Add a workflow step."""
254
- opts = StepOptions(
255
- agent=agent,
256
- task=task,
257
- depends_on=depends_on,
258
- verification=verification,
259
- timeout_ms=timeout_ms,
260
- retries=retries,
261
- )
262
- step_def: dict[str, Any] = {
263
- "name": name,
264
- "agent": opts.agent,
265
- "task": opts.task,
266
- }
267
-
268
- if opts.depends_on is not None:
269
- step_def["dependsOn"] = opts.depends_on
270
- if opts.verification is not None:
271
- step_def["verification"] = opts.verification.to_dict()
272
- if opts.timeout_ms is not None:
273
- step_def["timeoutMs"] = opts.timeout_ms
274
- if opts.retries is not None:
275
- step_def["retries"] = opts.retries
266
+ if type == "deterministic":
267
+ if not command:
268
+ raise ValueError("deterministic steps must have a command")
269
+ if agent is not None or task is not None:
270
+ raise ValueError("deterministic steps must not have agent or task")
271
+ step_def: dict[str, Any] = {"name": name, "type": "deterministic", "command": command}
272
+ if capture_output is not None:
273
+ step_def["captureOutput"] = capture_output
274
+ if fail_on_error is not None:
275
+ step_def["failOnError"] = fail_on_error
276
+ if depends_on is not None:
277
+ step_def["dependsOn"] = depends_on
278
+ if verification is not None:
279
+ step_def["verification"] = verification.to_dict()
280
+ if timeout_ms is not None:
281
+ step_def["timeoutMs"] = timeout_ms
282
+ elif type == "worktree":
283
+ if agent is not None or task is not None:
284
+ raise ValueError("worktree steps must not have agent or task")
285
+ if not branch:
286
+ raise ValueError("worktree steps must have a branch")
287
+ step_def = {"name": name, "type": "worktree", "branch": branch}
288
+ if base_branch is not None:
289
+ step_def["baseBranch"] = base_branch
290
+ if path is not None:
291
+ step_def["path"] = path
292
+ if create_branch is not None:
293
+ step_def["createBranch"] = create_branch
294
+ if depends_on is not None:
295
+ step_def["dependsOn"] = depends_on
296
+ if timeout_ms is not None:
297
+ step_def["timeoutMs"] = timeout_ms
298
+ else:
299
+ # Agent step
300
+ if not agent or not task:
301
+ raise ValueError("Agent steps must have both agent and task")
302
+ step_def = {"name": name, "agent": agent, "task": task}
303
+ if depends_on is not None:
304
+ step_def["dependsOn"] = depends_on
305
+ if verification is not None:
306
+ step_def["verification"] = verification.to_dict()
307
+ if timeout_ms is not None:
308
+ step_def["timeoutMs"] = timeout_ms
309
+ if retries is not None:
310
+ step_def["retries"] = retries
276
311
 
277
312
  self._steps.append(step_def)
278
313
  return self
@@ -302,11 +337,15 @@ class WorkflowBuilder:
302
337
 
303
338
  def to_config(self) -> dict[str, Any]:
304
339
  """Build and return the config as a dictionary (RelayYamlConfig shape)."""
305
- if not self._agents:
306
- raise ValueError("Workflow must have at least one agent")
307
340
  if not self._steps:
308
341
  raise ValueError("Workflow must have at least one step")
309
342
 
343
+ has_agent_steps = any(
344
+ s.get("type") not in ("deterministic", "worktree") for s in self._steps
345
+ )
346
+ if has_agent_steps and not self._agents:
347
+ raise ValueError("Workflow must have at least one agent when using agent steps")
348
+
310
349
  swarm: dict[str, Any] = {"pattern": self._pattern}
311
350
  if self._max_concurrency is not None:
312
351
  swarm["maxConcurrency"] = self._max_concurrency
@@ -0,0 +1,6 @@
1
+ """Agent Relay Communicate Mode -- put any agent on the relay."""
2
+
3
+ from .core import Relay, on_relay
4
+ from .types import Message, RelayConfig
5
+
6
+ __all__ = ["Relay", "Message", "RelayConfig", "on_relay"]
@@ -0,0 +1,138 @@
1
+ """Bridge that connects an external A2A agent into a Relay workspace."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from typing import Any
7
+
8
+ import aiohttp
9
+
10
+ from .a2a_types import A2AAgentCard, A2AConfig, A2AMessage, A2APart
11
+ from .core import Relay
12
+ from .types import Message, RelayConfig
13
+
14
+
15
+ class A2ABridge:
16
+ """Bridges an external A2A agent into a Relay workspace.
17
+
18
+ - Registers a proxy agent on the Relay workspace
19
+ - When Relay messages arrive for the proxy, forwards them as A2A JSON-RPC
20
+ message/send calls to the external agent
21
+ - When A2A responses come back, forwards them as Relay DMs
22
+
23
+ Usage:
24
+ bridge = A2ABridge(
25
+ relay_config=RelayConfig(workspace="myworkspace", api_key="rk_..."),
26
+ a2a_agent_url="https://partner-billing-agent.example.com",
27
+ proxy_name="partner-billing",
28
+ )
29
+ await bridge.start()
30
+ # Now "partner-billing" appears as an agent in the Relay workspace
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ relay_config: RelayConfig,
36
+ a2a_agent_url: str,
37
+ proxy_name: str,
38
+ ) -> None:
39
+ self.relay = Relay(proxy_name, relay_config)
40
+ self.a2a_agent_url = a2a_agent_url.rstrip("/")
41
+ self.proxy_name = proxy_name
42
+ self._session: aiohttp.ClientSession | None = None
43
+ self._agent_card: A2AAgentCard | None = None
44
+ self._started = False
45
+
46
+ async def start(self) -> None:
47
+ """Register proxy on Relay, listen for messages, forward to A2A agent."""
48
+ await self.relay.__aenter__()
49
+ self.relay.on_message(self._handle_relay_message)
50
+ self._started = True
51
+
52
+ async def stop(self) -> None:
53
+ """Disconnect from Relay and clean up."""
54
+ self._started = False
55
+ await self.relay.__aexit__(None, None, None)
56
+ if self._session is not None and not self._session.closed:
57
+ await self._session.close()
58
+ self._session = None
59
+
60
+ async def discover_agent(self) -> A2AAgentCard:
61
+ """Fetch the external A2A agent's Agent Card."""
62
+ session = await self._ensure_session()
63
+ url = f"{self.a2a_agent_url}/.well-known/agent.json"
64
+ async with session.get(url) as resp:
65
+ data = await resp.json()
66
+ self._agent_card = A2AAgentCard(
67
+ name=data.get("name", ""),
68
+ description=data.get("description", ""),
69
+ url=data.get("url", self.a2a_agent_url),
70
+ version=data.get("version", "1.0.0"),
71
+ )
72
+ return self._agent_card
73
+
74
+ async def send_a2a_message(self, text: str) -> str | None:
75
+ """Send a message/send JSON-RPC call to the external A2A agent.
76
+
77
+ Returns the response text if available.
78
+ """
79
+ session = await self._ensure_session()
80
+
81
+ a2a_msg = {
82
+ "role": "user",
83
+ "parts": [{"text": text}],
84
+ "messageId": str(uuid.uuid4()),
85
+ }
86
+
87
+ jsonrpc_request = {
88
+ "jsonrpc": "2.0",
89
+ "method": "message/send",
90
+ "params": {"message": a2a_msg},
91
+ "id": str(uuid.uuid4()),
92
+ }
93
+
94
+ target_url = self.a2a_agent_url
95
+ if self._agent_card and self._agent_card.url:
96
+ target_url = self._agent_card.url
97
+
98
+ async with session.post(target_url, json=jsonrpc_request) as resp:
99
+ data = await resp.json()
100
+
101
+ result = data.get("result", {})
102
+ # Extract response text from the task
103
+ status = result.get("status", {})
104
+ status_msg = status.get("message", {})
105
+ parts = status_msg.get("parts", [])
106
+ if parts:
107
+ return parts[0].get("text")
108
+
109
+ # Try from messages list
110
+ messages = result.get("messages", [])
111
+ for msg in reversed(messages):
112
+ if msg.get("role") == "agent":
113
+ msg_parts = msg.get("parts", [])
114
+ if msg_parts:
115
+ return msg_parts[0].get("text")
116
+
117
+ return None
118
+
119
+ async def _handle_relay_message(self, msg: Message) -> None:
120
+ """Forward Relay message to A2A agent, then forward response back."""
121
+ response_text = await self.send_a2a_message(msg.text)
122
+ if response_text:
123
+ await self.relay.send(msg.sender, response_text)
124
+
125
+ async def _ensure_session(self) -> aiohttp.ClientSession:
126
+ if self._session is None or self._session.closed:
127
+ self._session = aiohttp.ClientSession()
128
+ return self._session
129
+
130
+ async def __aenter__(self) -> "A2ABridge":
131
+ await self.start()
132
+ return self
133
+
134
+ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
135
+ await self.stop()
136
+
137
+
138
+ __all__ = ["A2ABridge"]