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.
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})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")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")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"],
})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"...)
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...")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...")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.
- API reference: ctx.stream
- How Friday Agents Work — NATS subprocess protocol details