Skip to content

Commit d5db0e0

Browse files
authored
Merge pull request #492 from conductor-oss/fix/wmq-examples
Fix WMQ examples (75-77)
2 parents eba5477 + 5f649cc commit d5db0e0

3 files changed

Lines changed: 70 additions & 42 deletions

File tree

examples/agents/75_wait_for_message.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
Demonstrates:
44
- wait_for_message_tool: dequeues messages from the WMQ (Conductor PULL_WORKFLOW_MESSAGES task)
55
- Mixing a server-side message tool with a local Python action tool
6-
- Looping agent that keeps processing messages indefinitely
6+
- Open-ended looping agent: it never decides to stop on its own
77
- Pushing messages from outside the workflow with runtime.send_message()
8+
- handle.stop() ending the loop deterministically
89
9-
The agent loops forever: each iteration waits for a message, reads the
10-
"task" field, executes it, and goes back to listening.
10+
Each iteration waits for a message, reads the "task" field, executes it, and
11+
goes back to listening. The agent's instructions tell it to never stop, so the
12+
loop only ends when the caller calls handle.stop(): that sets the
13+
``_stop_requested`` workflow variable checked by the DoWhile condition and
14+
pushes a ``{"_signal": "stop"}`` message to unblock the pending
15+
PULL_WORKFLOW_MESSAGES. The workflow ends COMPLETED, not TERMINATED — see
16+
84_deterministic_stop.py.
1117
1218
Requirements:
1319
- Conductor server with WMQ support (conductor.workflow-message-queue.enabled=true)
@@ -67,6 +73,7 @@ def main() -> None:
6773

6874
# Let the agent process all messages (~5-6s per message)
6975
time.sleep(30)
76+
# The agent will never stop on its own — end the loop from here.
7077
handle.stop()
7178
handle.join(timeout=30)
7279
print("\nDone.")

examples/agents/76_wait_for_message_streaming.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44
- wait_for_message_tool with streaming: push messages in and see the agent react
55
- Using handle.stream() to observe WAITING → processing → WAITING cycles
66
- runtime.send_message() to push payloads into the Workflow Message Queue
7+
- handle.stop() ending the loop deterministically
78
8-
The agent starts, immediately waits for a message, processes whatever it
9-
receives (by calling wait_for_message again), then waits again. The caller
10-
drives the conversation by sending messages and reading streamed events.
9+
The agent starts, immediately waits for a message, answers it with respond(),
10+
then loops back to wait_for_message. The caller drives the conversation from a
11+
background thread — sending a task every 8 seconds — while the main thread reads
12+
streamed events.
13+
14+
The agent's instructions tell it to never stop, so the loop only ends when the
15+
sender calls handle.stop() — after giving the last task time to be answered.
16+
That sets the ``_stop_requested`` workflow variable
17+
checked by the DoWhile condition and pushes a ``{"_signal": "stop"}`` message to
18+
unblock the pending PULL_WORKFLOW_MESSAGES. stream() then yields DONE.
1119
1220
Requirements:
1321
- Conductor server running at http://localhost:8080
@@ -66,15 +74,16 @@ def main() -> None:
6674
print(f"Agent started: {handle.execution_id}\n")
6775

6876
# Push messages from a background thread while we stream events on the main thread.
69-
# Wait long enough between sends for the agent to finish processing each message.
70-
# No sleep after the last send — handle.stream() on the main thread is already the
71-
# barrier: it blocks until DONE, which only fires once the workflow reaches a
72-
# terminal state (after stop() sets the flag and the current iteration completes).
77+
# Wait long enough between sends for the agent to finish processing each message
78+
# including after the last one. Calling stop() immediately after the final send
79+
# would set _stop_requested while the agent is still mid-turn on that task, and the
80+
# DoWhile would exit before it ever answers.
7381
def sender():
7482
for task in TASKS:
7583
time.sleep(8)
7684
print(f"\n [caller] sending -> {task!r}")
7785
runtime.send_message(handle.execution_id, {"task": task})
86+
time.sleep(8)
7887
handle.stop()
7988

8089
threading.Thread(target=sender, daemon=True).start()

examples/agents/77_kafka_consumer_agent.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -58,36 +58,48 @@ def echo_message(value: str, topic: str, offset: int) -> str:
5858
)
5959

6060

61-
with AgentRuntime() as runtime:
62-
handle = runtime.start(agent, "Start consuming messages from Kafka.")
63-
print(f"Agent started: {handle.execution_id}")
64-
65-
consumer = Consumer(
66-
{
67-
"bootstrap.servers": KAFKA_BOOTSTRAP,
68-
"group.id": KAFKA_GROUP,
69-
"auto.offset.reset": "latest",
70-
}
71-
)
72-
consumer.subscribe([KAFKA_TOPIC])
73-
try:
74-
while True:
75-
msg = consumer.poll(timeout=1.0)
76-
if msg is None:
77-
continue
78-
if msg.error():
79-
if msg.error().code() == KafkaError._PARTITION_EOF:
61+
def main() -> None:
62+
with AgentRuntime() as runtime:
63+
handle = runtime.start(agent, "Start consuming messages from Kafka.")
64+
print(f"Agent started: {handle.execution_id}")
65+
66+
consumer = Consumer(
67+
{
68+
"bootstrap.servers": KAFKA_BOOTSTRAP,
69+
"group.id": KAFKA_GROUP,
70+
"auto.offset.reset": "latest",
71+
}
72+
)
73+
consumer.subscribe([KAFKA_TOPIC])
74+
print(f"Consuming '{KAFKA_TOPIC}' from {KAFKA_BOOTSTRAP} — Ctrl+C to stop.")
75+
try:
76+
while True:
77+
msg = consumer.poll(timeout=1.0)
78+
if msg is None:
8079
continue
81-
raise RuntimeError(f"Kafka error: {msg.error()}")
82-
runtime.send_message(
83-
handle.execution_id,
84-
{
85-
"topic": msg.topic(),
86-
"partition": msg.partition(),
87-
"offset": msg.offset(),
88-
"key": msg.key().decode("utf-8") if msg.key() else None,
89-
"value": msg.value().decode("utf-8") if msg.value() else "",
90-
},
91-
)
92-
finally:
93-
consumer.close()
80+
if msg.error():
81+
if msg.error().code() == KafkaError._PARTITION_EOF:
82+
continue
83+
raise RuntimeError(f"Kafka error: {msg.error()}")
84+
runtime.send_message(
85+
handle.execution_id,
86+
{
87+
"topic": msg.topic(),
88+
"partition": msg.partition(),
89+
"offset": msg.offset(),
90+
"key": msg.key().decode("utf-8") if msg.key() else None,
91+
"value": msg.value().decode("utf-8") if msg.value() else "",
92+
},
93+
)
94+
except KeyboardInterrupt:
95+
print("\nStopping agent...")
96+
handle.stop()
97+
finally:
98+
consumer.close()
99+
100+
101+
# Guard the runtime block: spawned tool workers re-import this module, and
102+
# without the guard they would re-run the orchestration (multiprocessing's
103+
# "Safe importing of main module" error).
104+
if __name__ == "__main__":
105+
main()

0 commit comments

Comments
 (0)