# 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.

- Source: https://oharu121.com/blog/python-yield-async-generator-agent-streaming-input/
- Published: 2026-07-12T12:00:00+09:00
- Tags: Python, Node.js, AI Agent

---
## 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.

```python
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 time
```

### yield "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.

```python
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))  # 1
print(next(counter))  # 2 (remembers the state n=1)
print(next(counter))  # 3
```

*Figure — ReturnVsYield: `return` 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.

```python
# conventional: pass all input up front
response = 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.

```python
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.

*Figure — Dataflow: Input flows into a running agent; output flows back out of it.*

### 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.

```python
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.

```python
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`.

```javascript
// a Node.js async generator
async 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

- **`yield` returns a value without ending the function.** If `return` is a "letter", `yield` is 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.
