Brainby arc-labs/docs
Python SDK

Pipelines resource

Run and stream the Recall write/search pipelines from Python — including SSE-based progressive event streams.

The non-streaming pipelines.write(...) is functionally identical to memories.write(...) — both POST to /v1/remember. The pipelines resource adds two streaming methods that return generators of SseEvent instances, exposing the seven stages of the write pipeline and the read pipeline as they run.

In multi-tenant servers, bind a per-user context with for_user() before calling any pipelines method:

# All pipeline methods are available on the bound view.
result = recall.for_user(user_id).pipelines.write(messages=[
    {'role': 'user', 'content': 'I prefer dark mode.'},
])

The bound view sets X-Recall-User-ID on every request and is otherwise identical to the root client. It does not construct a new HTTP connection.

write()

POST/v1/rememberAPI keystable
def write(
    self,
    messages: list[dict[str, str]],
    *,
    idempotency_key: Optional[str] = None,
) -> Any: ...

Non-streaming write — alias for memories.write(). Returns the final result dict in one shot.

result = recall.pipelines.write(messages=[
    {'role': 'user', 'content': 'I work at Arc Labs.'},
])
result = await recall.pipelines.write(messages=[
    {'role': 'user', 'content': 'I work at Arc Labs.'},
])

write_stream()

POST/v1/remember/streamAPI keystable
def write_stream(
    self,
    messages: list[dict[str, str]],
) -> Generator[SseEvent, None, None]: ...

async def write_stream(
    self,
    messages: list[dict[str, str]],
) -> AsyncGenerator[SseEvent, None]: ...

Stream the write pipeline. Yields SseEvent instances for each stage (pipeline_init, stage_started, stage, llm_call, result, error, done). The connection closes when the server emits the OpenAI-style [DONE] sentinel.

Examples

for event in recall.pipelines.write_stream(messages=[
    {'role': 'user', 'content': 'I work at Arc Labs.'},
    {'role': 'assistant', 'content': 'Got it.'},
]):
    if event.event == 'stage':
        print('Stage:', event.data.get('stage'))
    elif event.event == 'result':
        print('Memories:', event.data['memoryIds'])
    elif event.event == 'error':
        print('Error:', event.data)
async for event in recall.pipelines.write_stream(messages=[
    {'role': 'user', 'content': 'I work at Arc Labs.'},
    {'role': 'assistant', 'content': 'Got it.'},
]):
    if event.event == 'stage':
        print('Stage:', event.data.get('stage'))
    elif event.event == 'result':
        print('Memories:', event.data['memoryIds'])

When to use

  • Surface pipeline progress in a UI ("extracting…", "deduping…", "indexing…").
  • Build interactive debugging tools that show what the LLM extracted before the dedupe stage filtered candidates.
  • Monitor latency stage-by-stage in production.

For straight-line "give me the IDs of the memories you wrote", use pipelines.write() or memories.write() — they're cheaper because they don't allocate the SSE buffer.

search_stream()

POST/v1/search/streamAPI keystable
def search_stream(
    self,
    query: str,
    limit: Optional[int] = None,
) -> Generator[SseEvent, None, None]: ...

# AsyncRecall:
async def search_stream(
    self,
    query: str,
    limit: Optional[int] = None,
) -> AsyncGenerator[SseEvent, None]: ...

Stream the read pipeline. Yields events for each retriever as it completes, then the fused result, then done.

ParameterTypeRequired
querystrrequired
Natural-language query.
limitintoptional

Server clamps to [1, 100]. Default 10. Renamed from top_k in 0.3.0.

Examples

for event in recall.pipelines.search_stream(
    query='UI preferences',
    limit=5,
):
    if event.event == 'stage':
        print('Stage:', event.data.get('stage'))
    elif event.event == 'result':
        for hit in event.data['results']:
            print(hit['content'])
async for event in recall.pipelines.search_stream(
    query='UI preferences',
    limit=5,
):
    if event.event == 'result':
        for hit in event.data['results']:
            print(hit['content'])

When to use

  • Render a "Recall is thinking…" indicator with stage detail in a chat UI.
  • Debug retrieval — see which retriever fired first and what scores it produced before fusion.
  • Build a streaming dashboard that shows search latency broken down by retriever.

SSE event types

event.event is one of:

OptionTypeDefault / Env
pipeline_initstr

Sent once at start. Carries trace IDs, the input message count, and the chosen retrievers.

stage_startedstr

Marks the beginning of a pipeline stage. event.data.stage carries the stage name (extract, classify, ground, dedupe, embed, persist, index for write; rewrite, retrieve, fuse, filter for read).

stagestr

Emitted on stage completion with timing and counts.

llm_callstr

LLM-backed stages emit one event per call with provider, model, and token-usage details. Useful for cost attribution.

resultstr

The terminal payload — memory IDs for write, search hits for read.

errorstr

Stage failed. event.data carries code, message, trace_id. The stream ends after an error event.

donestr

Synthetic event emitted by the SSE parser when the server sends [DONE]. Confirms clean shutdown.

See Streaming for the full SSE parser internals.

Iteration patterns

The sync stream is a Generator — iterate with for:

for event in recall.pipelines.write_stream(messages=msgs):
    ...

The async stream is an AsyncGenerator — iterate with async for:

async for event in recall.pipelines.write_stream(messages=msgs):
    ...

Note that async streams must be iterated inside an async function. You cannot list(...) an AsyncGenerator — use a comprehension:

events = [event async for event in recall.pipelines.write_stream(messages=msgs)]

Error handling

A non-2xx response when opening the stream raises a typed error immediately — RecallAuthError on 401/403, RecallNotFoundError on 404, RecallValidationError on 422, RecallRateLimitError on 429, RecallServerError on 5xx. The transport reads the error envelope from the streaming response body and constructs the right error class.

from recall.errors import RecallAuthError

try:
    for event in recall.pipelines.write_stream(messages=msgs):
        handle(event)
except RecallAuthError as exc:
    print('auth failed:', exc.code, exc.request_id)

Errors emitted mid-stream (after the connection is open) arrive as event.event == 'error' events. Inspect event.data and break out of the loop:

for event in stream:
    if event.event == 'error':
        raise RuntimeError(f"pipeline failed: {event.data}")

Idempotency

pipelines.write() carries a Recall-Idempotency-Key header (UUID v4 auto-generated unless you override). The streaming variants do not — SSE streams are not retriable in the same way. If a streaming write fails mid-stream, you must retry the whole call.

Was this page helpful?

On this page