The Pi Coding Agent community has a packaging problem: workflows that should be 20 lines of config become 200-line packages with unnecessary abstractions. This tutorial shows how to build minimal, single-purpose search workflows for Pi that compose well without over-engineering.
Prerequisites
- Pi Coding Agent installed (v0.73+)
- A Scavio API key
- Understanding of Pi workflow basics
Walkthrough
Step 1: The minimal search workflow pattern
A Pi workflow that does one thing: search and return results. No orchestration, no state machine.
# ~/.pi-agent/workflows/quick_search.yaml
name: quick_search
trigger: /search
steps:
- action: http_post
url: https://api.scavio.dev/api/v2/google
headers:
Authorization: Bearer ${SCAVIO_API_KEY}
Content-Type: application/json
body:
query: ${input}
output: results
- action: format
template: |
${results.organic[0:5].map(r => '- ' + r.title + ': ' + r.snippet).join('\n')}Step 2: Compose multiple minimal workflows
Build separate workflows for each platform, compose them when needed.
# ~/.pi-agent/workflows/reddit_search.yaml
name: reddit_search
trigger: /reddit
steps:
- action: http_post
url: https://api.scavio.dev/api/v1/reddit/search
headers:
Authorization: Bearer ${SCAVIO_API_KEY}
Content-Type: application/json
body:
query: ${input}
output: results
- action: format
template: |
${results.organic[0:5].map(r => '- [' + r.title + '](' + r.link + ') (score: ' + r.score + ')').join('\n')}
# Usage: /reddit python fastapi deployment tipsStep 3: Avoid the over-packaging trap
Compare minimal vs over-packaged approaches.
# OVER-PACKAGED (don't do this):
# - 50-line config with state machine
# - Custom error handling framework
# - Retry logic with exponential backoff
# - Result caching layer
# - Analytics tracking
# - Output formatters for 5 different formats
# Total: 200+ lines, hard to debug, breaks often
# MINIMAL (do this):
# - HTTP POST to search API
# - Format results
# - Done
# Total: 12 lines, easy to debug, rarely breaks
# If you need retries, add them as a SEPARATE composable workflow:
# ~/.pi-agent/workflows/retry_wrapper.yaml
name: retry_wrapper
steps:
- action: retry
max_attempts: 2
workflow: ${target_workflow}
input: ${input}Step 4: Multi-platform research as composition
Compose minimal workflows into a research workflow without a framework.
# ~/.pi-agent/workflows/research.yaml
name: research
trigger: /research
steps:
- action: parallel
workflows:
- quick_search: ${input}
- reddit_search: ${input}
output: all_results
- action: format
template: |
## Google Results
${all_results[0]}
## Reddit Discussions
${all_results[1]}
# Usage: /research best database for side projects 2026
# Runs Google + Reddit search in parallel, formats combined outputPython Example
import requests, os
# Scavio has one endpoint per platform - there is no dispatcher endpoint and no
# `platform` request param, so the selector lives in your code.
SCAVIO = "https://api.scavio.dev"
SCAVIO_ENDPOINTS = {
"google": "/api/v2/google",
"reddit": "/api/v1/reddit/search",
"youtube": "/api/v1/youtube/search",
"amazon": "/api/v1/amazon/search",
"walmart": "/api/v1/walmart/search",
}
SCAVIO_QUERY_KEY = {"youtube": "search"}
SCAVIO_RESULTS_KEY = {"google": "organic_results", "reddit": "results",
"youtube": "results", "amazon": "products", "walmart": "products"}
def scavio_url(platform):
return SCAVIO + SCAVIO_ENDPOINTS[platform or "google"]
def scavio_body(platform, query):
return {SCAVIO_QUERY_KEY.get(platform or "google", "query"): query}
def scavio_payload(payload, platform="google"):
"""Google v2 passes Google's response through as-is; every other endpoint
wraps its payload in `data`. Item fields differ per platform (see
https://scavio.dev/docs), so only the result list is normalised here."""
platform = platform or "google"
out = payload if platform == "google" else payload["data"]
return {**out, "results": out.get(SCAVIO_RESULTS_KEY[platform], [])}
def pi_search(query: str, platform: str = 'google') -> str:
r = scavio_payload(requests.post(scavio_url(platform), headers={'Authorization': 'Bearer ' + os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}, json=scavio_body(platform, query)).json(), platform)
return '\n'.join(f"- {x['title']}: {x.get('snippet','')}" for x in r.get('results',[])[:5])JavaScript Example
// Scavio has one endpoint per platform - there is no dispatcher endpoint and no
// `platform` request param, so the selector lives in your code.
const SCAVIO = "https://api.scavio.dev";
const SCAVIO_ENDPOINTS = {
google: "/api/v2/google",
reddit: "/api/v1/reddit/search",
youtube: "/api/v1/youtube/search",
amazon: "/api/v1/amazon/search",
walmart: "/api/v1/walmart/search",
};
const SCAVIO_QUERY_KEY = { youtube: "search" };
const SCAVIO_RESULTS_KEY = { google: "organic_results", reddit: "results",
youtube: "results", amazon: "products", walmart: "products" };
const scavioUrl = (platform) => SCAVIO + SCAVIO_ENDPOINTS[platform || "google"];
const scavioBody = (platform, query) =>
({ [SCAVIO_QUERY_KEY[platform || "google"] || "query"]: query });
// Google v2 passes Google's response through as-is; every other endpoint wraps
// its payload in `data`. Item fields differ per platform (see
// https://scavio.dev/docs), so only the result list is normalised here.
function scavioPayload(json, platform = "google") {
const p = platform || "google";
const out = p === "google" ? json : json.data;
return { ...out, results: out[SCAVIO_RESULTS_KEY[p]] || [] };
}
async function piSearch(query, platform = 'google') {
const r = await fetch(scavioUrl("google"), {
method: 'POST', headers: {'Authorization': `Bearer ${process.env.SCAVIO_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify(scavioBody("google", query))
});
return (scavioPayload(await r.json(), "google")).organic?.slice(0,5).map(x => `- ${x.title}: ${x.snippet}`).join('\n');
}Expected Output
Minimal, composable Pi Coding Agent workflows that do one thing well (search one platform) and compose into multi-platform research without over-engineering.