Skip to content

Commit fd292e4

Browse files
committed
fix(agents,examples): spawn-safety + main-guards, model ids, server URLs; add run-all harness
GPTAssistantAgent's assistant tool becomes a module-level picklable callable (spawn-safe). Examples: add if-__main__ guards to stateful/looping workers (75/76/82/83/84), hoist <locals> tools to module level (92, 16g) and make 82 spawn-safe (env-shared IPC + client rebuild), use settings.llm_model in 58, read server URL from env in the schedule examples, add missing 'import sys' (72/73). Adds run_all_examples.py harness and gitignores its generated report + coding-agent output.
1 parent 0fd382f commit fd292e4

15 files changed

Lines changed: 807 additions & 346 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ __pycache__/
33
*.py[cod]
44
*$py.class
55

6+
# Example-run artifacts (generated by run_all_examples.py / coding-agent examples)
7+
examples/agents/run_report.html
8+
examples/agents/codingexamples/
9+
610
# C extensions
711
*.so
812

examples/agents/16g_credentials_framework_passthrough.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,28 @@
2424

2525
import os
2626

27+
from langchain_core.tools import tool as lc_tool
28+
2729
from conductor.ai.agents import AgentRuntime
2830
from settings import settings
2931

3032

33+
# Module-level tool (spawn-safe: importable by qualified name). A <locals>
34+
# tool defined inside the factory can't be resolved by a spawned worker.
35+
@lc_tool
36+
def check_github_auth() -> str:
37+
"""Check if GitHub authentication is available."""
38+
token = os.environ.get("GITHUB_TOKEN", "")
39+
if token:
40+
return f"GitHub token is set (starts with {token[:4]}...)"
41+
return "GitHub token is NOT set"
42+
43+
3144
def create_langgraph_agent():
3245
"""Create a simple LangGraph agent with a tool that uses GITHUB_TOKEN."""
33-
from langchain_core.tools import tool as lc_tool
3446
from langchain_openai import ChatOpenAI
3547
from langgraph.prebuilt import create_react_agent
3648

37-
@lc_tool
38-
def check_github_auth() -> str:
39-
"""Check if GitHub authentication is available."""
40-
token = os.environ.get("GITHUB_TOKEN", "")
41-
if token:
42-
return f"GitHub token is set (starts with {token[:4]}...)"
43-
return "GitHub token is NOT set"
44-
4549
# Parse provider/model format
4650
model_str = settings.llm_model
4751
if "/" in model_str:

examples/agents/58_scatter_gather.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def search_knowledge_base(query: str) -> dict:
5050

5151
researcher = Agent(
5252
name="researcher",
53-
model="anthropic/claude-sonnet-4-20250514",
53+
model=settings.llm_model,
5454
instructions=(
5555
"You are a country analyst. You will be given the name of a country. "
5656
"Use the search_knowledge_base tool ONCE to research that country, then "

examples/agents/72_client_reconnect.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import json
2727
import os
2828
import signal
29+
import sys
2930
import time
3031
from pathlib import Path
3132

examples/agents/73_worker_restart_recovery.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import json
2525
import os
2626
import signal
27+
import sys
2728
import time
2829
from datetime import UTC, datetime
2930
from pathlib import Path

examples/agents/75_wait_for_message.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,26 @@ def execute_task(task: str) -> str:
5757
),
5858
)
5959

60-
with AgentRuntime() as runtime:
61-
handle = runtime.start(agent, "Start listening for messages.")
62-
print(f"Agent started: {handle.execution_id}")
63-
print("Sending messages...\n")
64-
65-
for msg in ["summarize quarterly report", "draft release notes", "check system health"]:
66-
time.sleep(2)
67-
print(f" -> sending: {msg!r}")
68-
runtime.send_message(handle.execution_id, {"task": msg})
69-
70-
# Let the agent process all messages (~5-6s per message)
71-
time.sleep(30)
72-
handle.stop()
73-
handle.join(timeout=30)
74-
print("\nDone.")
60+
def main() -> None:
61+
with AgentRuntime() as runtime:
62+
handle = runtime.start(agent, "Start listening for messages.")
63+
print(f"Agent started: {handle.execution_id}")
64+
print("Sending messages...\n")
65+
66+
for msg in ["summarize quarterly report", "draft release notes", "check system health"]:
67+
time.sleep(2)
68+
print(f" -> sending: {msg!r}")
69+
runtime.send_message(handle.execution_id, {"task": msg})
70+
71+
# Let the agent process all messages (~5-6s per message)
72+
time.sleep(30)
73+
handle.stop()
74+
handle.join(timeout=30)
75+
print("\nDone.")
76+
77+
78+
# Guard the runtime block: spawned tool workers re-import this module, and
79+
# without the guard they would re-run the orchestration (multiprocessing's
80+
# "Safe importing of main module" error).
81+
if __name__ == "__main__":
82+
main()

examples/agents/76_wait_for_message_streaming.py

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -63,38 +63,46 @@ def respond(answer: str) -> str:
6363
"Write a one-line Python function that reverses a string",
6464
]
6565

66-
with AgentRuntime() as runtime:
67-
handle = runtime.start(agent, "Begin. Wait for your first instruction.")
68-
print(f"Agent started: {handle.execution_id}\n")
69-
70-
# Push messages from a background thread while we stream events on the main thread.
71-
# Wait long enough between sends for the agent to finish processing each message.
72-
# No sleep after the last send — handle.stream() on the main thread is already the
73-
# barrier: it blocks until DONE, which only fires once the workflow reaches a
74-
# terminal state (after stop() sets the flag and the current iteration completes).
75-
def sender():
76-
for task in TASKS:
77-
time.sleep(8)
78-
print(f"\n [caller] sending -> {task!r}")
79-
runtime.send_message(handle.execution_id, {"task": task})
80-
handle.stop()
81-
82-
threading.Thread(target=sender, daemon=True).start()
83-
84-
for event in handle.stream():
85-
if event.type == EventType.THINKING:
86-
print(f" [thinking] {event.content}")
87-
88-
elif event.type == EventType.TOOL_CALL and event.tool_name == "respond":
89-
args = event.args or {}
90-
print(f" [answer] {args.get('answer', '')}")
91-
92-
elif event.type == EventType.WAITING:
93-
print(f" [waiting] {event.content}")
94-
95-
elif event.type == EventType.ERROR:
96-
print(f" [error] {event.content}")
97-
98-
elif event.type == EventType.DONE:
99-
print(f"\nAgent finished: {event.output}")
100-
break
66+
def main() -> None:
67+
with AgentRuntime() as runtime:
68+
handle = runtime.start(agent, "Begin. Wait for your first instruction.")
69+
print(f"Agent started: {handle.execution_id}\n")
70+
71+
# Push messages from a background thread while we stream events on the main thread.
72+
# Wait long enough between sends for the agent to finish processing each message.
73+
# No sleep after the last send — handle.stream() on the main thread is already the
74+
# barrier: it blocks until DONE, which only fires once the workflow reaches a
75+
# terminal state (after stop() sets the flag and the current iteration completes).
76+
def sender():
77+
for task in TASKS:
78+
time.sleep(8)
79+
print(f"\n [caller] sending -> {task!r}")
80+
runtime.send_message(handle.execution_id, {"task": task})
81+
handle.stop()
82+
83+
threading.Thread(target=sender, daemon=True).start()
84+
85+
for event in handle.stream():
86+
if event.type == EventType.THINKING:
87+
print(f" [thinking] {event.content}")
88+
89+
elif event.type == EventType.TOOL_CALL and event.tool_name == "respond":
90+
args = event.args or {}
91+
print(f" [answer] {args.get('answer', '')}")
92+
93+
elif event.type == EventType.WAITING:
94+
print(f" [waiting] {event.content}")
95+
96+
elif event.type == EventType.ERROR:
97+
print(f" [error] {event.content}")
98+
99+
elif event.type == EventType.DONE:
100+
print(f"\nAgent finished: {event.output}")
101+
break
102+
103+
104+
# Guard the runtime block: spawned tool workers re-import this module, and
105+
# without the guard they would re-run the orchestration (multiprocessing's
106+
# "Safe importing of main module" error).
107+
if __name__ == "__main__":
108+
main()

0 commit comments

Comments
 (0)