Skip to content

Latest commit

 

History

History
149 lines (98 loc) · 3.89 KB

File metadata and controls

149 lines (98 loc) · 3.89 KB

How to Stream Progress Updates

Emit progress events that appear in Friday's UI during long-running operations.

New here? See Your First Friday Agent for how to build and run your agent.

Basic progress emission

from friday_agent_sdk import agent, ok

@agent(id="long-task", version="1.0.0", description="Takes a while")
def execute(prompt, ctx):
    ctx.stream.progress("Starting analysis...")

    # Do work...
    result = ctx.llm.generate(...)

    ctx.stream.progress("Processing results...")

    # More work...
    data = process(result.text)

    ctx.stream.progress("Complete!")
    return ok({"data": data})

Intent emission

Emit high-level intents for significant state changes:

ctx.stream.intent("Analysing repository structure")

# Walk directory tree...

ctx.stream.intent("Identifying issues")

# Run analysis...

ctx.stream.intent("Generating report")

With tool context

Associate progress with specific tools:

ctx.stream.progress("Fetching repository data", tool_name="GitHub")

# Call GitHub MCP tools...

ctx.stream.progress("Analyzing code patterns", tool_name="Analyzer")

# LLM analysis...

ctx.stream.progress("Creating summary", tool_name="Reporter")

Real example: multi-phase agent

from friday_agent_sdk import agent, ok, AgentExtras

@agent(id="analyzer", version="1.0.0", description="Multi-phase analysis")
def execute(prompt, ctx):
    # Phase 1: Extract parameters
    ctx.stream.progress("Parsing request")
    params = extract_params(prompt)

    # Phase 2: LLM preprocessing
    ctx.stream.progress("Running initial analysis", tool_name="LLM")
    analysis = ctx.llm.generate(
        messages=[{"role": "user", "content": f"Analyze: {params}"}],
        model="claude-haiku-4-5",
    )

    # Phase 3: Tool calls
    ctx.stream.progress("Fetching related data", tool_name="GitHub")
    issues = ctx.tools.call("search_issues", {"query": params["query"]})

    # Phase 4: Synthesis
    ctx.stream.progress("Synthesising results", tool_name="Synthesiser")
    result = synthesise(analysis.text, issues)

    ctx.stream.progress("Analysis complete")
    return ok({
        "summary": result["summary"],
        "recommendations": result["recommendations"],
    })

When to emit

Emit progress when:

  • Starting a distinct phase of work
  • Before expensive operations (LLM calls, HTTP requests)
  • After completing significant milestones
  • When handling fallback scenarios ("Retrying with different model...")

Do not emit:

  • In tight loops (debounce or batch instead)
  • For trivial operations (< 100ms)
  • Excessively verbose detail ("Step 1 of 50", "Step 2 of 50"...)

Emitting during long operations

ctx.stream.progress() returns immediately — it does not wait for the host to process the event. This means you can emit progress before expensive operations and the UI updates right away:

ctx.stream.progress("Starting LLM call...")

# This blocks until the full response is ready
result = ctx.llm.generate(messages, model="claude-sonnet-4-6")

# Back in your code — emit the next update
ctx.stream.progress("LLM complete, processing...")

Fallback when stream unavailable

In test contexts without a host, ctx.stream is a no-op stub that safely ignores calls. You can call it unconditionally:

ctx.stream.progress("Working...")  # Safe even in tests

# Or use a helper for consistency
def progress(ctx, msg):
    ctx.stream.progress(msg)

progress(ctx, "Starting...")

Raw event emission

For custom event types, use emit():

ctx.stream.emit("custom-event", {"phase": "validation", "count": 42})

The data parameter accepts either a dict (JSON-serialised) or string.

See also