Skip to content

Commit eba5477

Browse files
authored
fix(examples): tool closures failing to pickle across spawned worker processes (#477)
1 parent cdd1114 commit eba5477

7 files changed

Lines changed: 836 additions & 686 deletions

File tree

examples/agents/16k_credentials_google_adk.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,22 @@
1919
from conductor.ai.agents import AgentRuntime
2020

2121

22+
def check_github_auth() -> str:
23+
"""Check if GitHub authentication is available."""
24+
token = os.environ.get("GITHUB_TOKEN", "")
25+
if token:
26+
return f"GitHub token is set (starts with {token[:4]}...)"
27+
return "GitHub token is NOT set"
28+
29+
2230
def create_adk_agent():
2331
"""Create a Google ADK agent with a credential-aware tool."""
2432
from google.adk import Agent
2533
from google.adk.tools import FunctionTool
2634

27-
def check_github_auth() -> str:
28-
"""Check if GitHub authentication is available."""
29-
token = os.environ.get("GITHUB_TOKEN", "")
30-
if token:
31-
return f"GitHub token is set (starts with {token[:4]}...)"
32-
return "GitHub token is NOT set"
33-
3435
agent = Agent(
3536
name="github_checker",
36-
model="gemini-2.5-flash",
37+
model="gemini-3.6-flash",
3738
instruction="You check GitHub authentication status.",
3839
tools=[FunctionTool(check_github_auth)],
3940
)

examples/agents/79_agent_message_bus.py

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -42,35 +42,43 @@
4242
from conductor.ai.agents import Agent, AgentRuntime, tool, wait_for_message_tool
4343
from settings import settings
4444

45-
# Shared directory for IPC between main process and worker processes.
46-
# Workers run as separate OS processes (different PIDs, same filesystem).
47-
_ipc_dir = Path(tempfile.mkdtemp(prefix="message_bus_"))
45+
_IPC_DIR_ENV = "MESSAGE_BUS_IPC_DIR"
46+
if _IPC_DIR_ENV in os.environ:
47+
_ipc_dir = Path(os.environ[_IPC_DIR_ENV])
48+
else:
49+
_ipc_dir = Path(tempfile.mkdtemp(prefix="message_bus_"))
50+
os.environ[_IPC_DIR_ENV] = str(_ipc_dir)
4851
_FORWARDED_DIR = _ipc_dir / "forwarded" # one file per forwarded topic
49-
_FORWARDED_DIR.mkdir()
52+
_FORWARDED_DIR.mkdir(exist_ok=True)
5053

5154
TOPICS = [
5255
"the impact of edge computing on cloud infrastructure",
5356
"why Rust is gaining adoption in systems programming",
5457
"how vector databases work",
5558
]
5659

60+
_WRITER_EXECUTION_ID_ENV = "MESSAGE_BUS_WRITER_EXECUTION_ID"
5761

58-
def build_researcher(runtime: AgentRuntime, writer_execution_id: str) -> Agent:
62+
63+
@tool
64+
def forward_to_writer(topic: str, notes: str) -> str:
65+
"""Forward research notes to the Writer and signal the main process."""
66+
print(f" [researcher → writer] forwarding notes on {topic!r}")
67+
writer_execution_id = os.environ[_WRITER_EXECUTION_ID_ENV]
68+
with AgentRuntime() as rt:
69+
rt.send_message(writer_execution_id, {"topic": topic, "notes": notes})
70+
(_FORWARDED_DIR / f"{time.time_ns()}.done").touch()
71+
return "forwarded"
72+
73+
74+
def build_researcher() -> Agent:
5975
"""Build the Researcher agent with a forward tool wired to the Writer's queue."""
6076

6177
receive_topic = wait_for_message_tool(
6278
name="wait_for_topic",
6379
description="Wait for the next research topic.",
6480
)
6581

66-
@tool
67-
def forward_to_writer(topic: str, notes: str) -> str:
68-
"""Forward research notes to the Writer and signal the main process."""
69-
print(f" [researcher → writer] forwarding notes on {topic!r}")
70-
runtime.send_message(writer_execution_id, {"topic": topic, "notes": notes})
71-
(_FORWARDED_DIR / f"{time.time_ns()}.done").touch()
72-
return "forwarded"
73-
7482
return Agent(
7583
name="researcher",
7684
model=settings.llm_model,
@@ -88,6 +96,14 @@ def forward_to_writer(topic: str, notes: str) -> str:
8896
)
8997

9098

99+
@tool
100+
def publish(topic: str, paragraph: str) -> str:
101+
"""Publish the finished paragraph."""
102+
print(f"\n [writer] ── {topic} ──")
103+
print(f" {paragraph}\n")
104+
return "published"
105+
106+
91107
def build_writer() -> Agent:
92108
"""Build the Writer agent that polishes research notes into paragraphs."""
93109

@@ -99,13 +115,6 @@ def build_writer() -> Agent:
99115
),
100116
)
101117

102-
@tool
103-
def publish(topic: str, paragraph: str) -> str:
104-
"""Publish the finished paragraph."""
105-
print(f"\n [writer] ── {topic} ──")
106-
print(f" {paragraph}\n")
107-
return "published"
108-
109118
return Agent(
110119
name="writer",
111120
model=settings.llm_model,
@@ -122,34 +131,41 @@ def publish(topic: str, paragraph: str) -> str:
122131
)
123132

124133

125-
try:
126-
with AgentRuntime() as runtime:
127-
# Start the Writer first so its execution_id is available to the Researcher
128-
writer_handle = runtime.start(build_writer(), "Begin. Wait for research notes.")
129-
writer_id = writer_handle.execution_id
130-
print(f"Writer started: {writer_id}")
131-
132-
researcher = build_researcher(runtime, writer_id)
133-
researcher_handle = runtime.start(researcher, "Begin. Wait for your first topic.")
134-
researcher_id = researcher_handle.execution_id
135-
print(f"Researcher started: {researcher_id}\n")
136-
137-
time.sleep(4)
138-
print("Sending topics to Researcher...\n")
139-
for topic in TOPICS:
140-
print(f" → {topic!r}")
141-
runtime.send_message(researcher_id, {"topic": topic})
142-
143-
# Wait until all topics have been forwarded to the Writer
144-
while len(list(_FORWARDED_DIR.iterdir())) < len(TOPICS):
145-
time.sleep(0.1)
146-
147-
# Deterministic stop — no stop-handling instructions needed.
148-
researcher_handle.stop()
149-
writer_handle.stop()
150-
researcher_handle.join(timeout=30)
151-
writer_handle.join(timeout=30)
152-
153-
print("Done.")
154-
finally:
155-
shutil.rmtree(_ipc_dir, ignore_errors=True)
134+
def main() -> None:
135+
try:
136+
with AgentRuntime() as runtime:
137+
# Start the Writer first so its execution_id is available to the Researcher
138+
writer_handle = runtime.start(build_writer(), "Begin. Wait for research notes.")
139+
writer_id = writer_handle.execution_id
140+
print(f"Writer started: {writer_id}")
141+
142+
os.environ[_WRITER_EXECUTION_ID_ENV] = writer_id
143+
144+
researcher = build_researcher()
145+
researcher_handle = runtime.start(researcher, "Begin. Wait for your first topic.")
146+
researcher_id = researcher_handle.execution_id
147+
print(f"Researcher started: {researcher_id}\n")
148+
149+
time.sleep(4)
150+
print("Sending topics to Researcher...\n")
151+
for topic in TOPICS:
152+
print(f" → {topic!r}")
153+
runtime.send_message(researcher_id, {"topic": topic})
154+
155+
# Wait until all topics have been forwarded to the Writer
156+
while len(list(_FORWARDED_DIR.iterdir())) < len(TOPICS):
157+
time.sleep(0.1)
158+
159+
# Deterministic stop — no stop-handling instructions needed.
160+
researcher_handle.stop()
161+
writer_handle.stop()
162+
researcher_handle.join(timeout=30)
163+
writer_handle.join(timeout=30)
164+
165+
print("Done.")
166+
finally:
167+
shutil.rmtree(_ipc_dir, ignore_errors=True)
168+
169+
170+
if __name__ == "__main__":
171+
main()

0 commit comments

Comments
 (0)