
Python's yield streams input into a running AI agent — the difference from return, in Node.js too
How Python's yield differs from return, and how an async generator passed as a prompt streams input into a long-running AI agent, with the Node.js equivalent.
On this page
Introduction
A long-running AI agent sometimes needs new information injected mid-execution (a human’s approval, an external event, a follow-up instruction) without recreating the session and resending its entire history. Python’s yield turns out to be the mechanism behind that pattern: a keyword simple enough to skim past without noticing it connects directly to agent architecture. The pattern is an async generator held open as the input stream: yield pauses a function with its state intact, so an agent’s session can stay “warm” while new messages arrive one at a time. This article works through yield from its difference with return, to that streaming-input pattern, to the same thing implemented in Node.js.
return and yield — a fundamental difference in control flow
return “exits immediately”
return ends the function on the spot. All local variables in the function are discarded, and the next call starts over from the first line.
def get_greeting(): name = "Alice" return f"Hello, {name}" # the function ends completely here; name is discarded too
result = get_greeting() # runs from the start every timeyield “pauses and resumes”
Using yield turns the function into a generator. It returns a value while pausing, with the function’s state (variables, execution position) all preserved. When the next value is requested, execution resumes from the line where it paused.
def count_up(): n = 1 while True: yield n # return n and pause n += 1 # on resume, continues from here
counter = count_up()print(next(counter)) # 1print(next(counter)) # 2 (remembers the state n=1)print(next(counter)) # 3return restarts from scratch; yield resumes where it stopped.Comparison summary
| Property | return |
yield |
|---|---|---|
| Function end | Ends immediately | Pauses (resumable) |
| State retention | Discarded | Preserved |
| Call model | Runs from the start every time | Resumes from the previous pause point |
| An analogy | Sending a letter (one and done) | Making a phone call (keeping the line open) |
Streaming input into an agent — the async iterator pattern
The yield property of “returning values one at a time while keeping state” connects directly to the input pattern for a long-running AI agent.
The problem: the limit of a single request
In an ordinary API call, all input must be present at request time.
# conventional: pass all input up frontresponse = await client.query(prompt="Analyse this data")But in a long-running agent, some situations call for injecting new information mid-execution. Recreating the session each time means resending the entire past conversation history, which is inefficient.
The solution: streaming input with an async generator
Passing a Python async generator as the prompt keeps the session “open”, dynamically feeding in messages that arrive later.
import asyncio
async def message_streamer(): # send the initial instruction yield "Starting the agent. Please analyse the S3 logs."
# wait for events from an external queue (SQS, etc.) and inject as they arrive while True: event = await queue.get() yield event.message if event.is_last: break
# pass the iterator to the SDK — the session is kept alive (pseudo-code)response = await client.query(prompt=message_streamer())The point is that thanks to yield, the function does not end and enters a “waiting state” until an event arrives in the queue. When an event arrives it resumes and feeds a new message to the agent.
The exact SDK entry point here is illustrative: the method that accepts an async iterable of messages goes by different names across SDKs (query, run, chat), and these APIs are evolving quickly, so check your SDK’s current signature. What is stable across them is the pattern: an async generator held open as the input stream.
Note the difference from streaming=True
The streaming=True you see in many SDKs is a setting for streaming the output (response). Passing an async iterator as the prompt, on the other hand, is a mechanism for streaming the input. The direction is reversed, so take care not to confuse them.
| Setting | Direction | Purpose |
|---|---|---|
streaming=True |
Output streaming | Receive the response token by token |
| Pass an async iterator as the prompt | Input streaming | Dynamically inject messages into a running agent |
Use cases — when streaming input is needed
1. Human-in-the-Loop (HITL)
When the agent asks for confirmation, such as “may I delete this file?”, the session can wait for the human’s response while staying alive.
async def hitl_streamer(): yield "Please start cleaning up the production database."
# when the agent asks for confirmation, receive the human's answer from the UI approval = await wait_for_human_approval() yield f"Approval: {approval}"The benefit is being able to insert human judgement while keeping the agent “warm”.
2. Injecting real-time external events
A case where a system event, such as “the customer upgraded their plan”, is injected while a support bot is mid-analysis of the same customer’s problem.
async def event_injector(): yield "Please analyse the support ticket for customer #12345."
async for event in system_event_stream: yield f"[SYSTEM EVENT: {event.description}]"The agent receives the event and can adjust the direction of its answer in real time.
3. Adding instructions incrementally
A case where, while analysing a 100-page document, an instruction like “actually, add this perspective too” gets injected mid-analysis. Because there is no need to recreate the session, the analysis context so far is preserved.
Implementing it in Node.js — async function*
The same pattern works not only in Python but in Node.js. The syntax is async function* (with an asterisk) and yield.
// a Node.js async generatorasync function* messageStreamer() { yield "Initializing agent...";
while (true) { const event = await waitForNextEvent(); yield event.text;
if (event.type === 'close') break; }}
// pass the iterator to the SDK (pseudo-code)const client = new AgentClient();await client.query({ prompt: messageStreamer() });Python vs Node.js comparison
| Item | Python | Node.js |
|---|---|---|
| Syntax | async def func(): + yield |
async function* func() + yield |
| How to consume | async for item in gen: |
for await (const item of gen) |
| Generator detection | Auto-detected by the presence of yield |
Explicit via the asterisk of function* |
| Ecosystem | asyncio-based | EventEmitter / Promise-based |
Node.js differs from Python in needing a * on the function declaration, but the concept is identical. In either language the behaviour is the same: “send out values one at a time without ending the function, keeping state preserved”.
Summary
yieldreturns a value without ending the function. Ifreturnis a “letter”,yieldis a “phone call” that keeps the line open for exchanging information.- It connects directly to streaming input for a long-running agent. Passing an async iterator as the prompt lets messages be dynamically injected into a running agent.
- It realises patterns common in practice — HITL, external-event injection, incremental instructions. The overhead of recreating the session is avoided in each one.
- The same concept works in Python and Node.js. There is a syntactic difference, but the design idea is shared.
yield is not merely a language feature; it is a foundation for AI agent design patterns, and the streaming-input pattern is worth reaching for whenever a long-running agent needs it.




