or: pip install pi-sidecar-client
CLIs after install: `pi-sidecar` (server) and `pi-sidecar-start` (dev start script).
### 2. Start the sidecar
**Production-style (default port 9100):**
```bash
npx pi-sidecar
# or: node dist/server.js
Local dev helper (default port 9201):
npx pi-sidecar-start
# foreground: npx pi-sidecar-start --foreground
# stop: npx pi-sidecar-start --stop
| Variable | Default (pi-sidecar) |
Default (pi-sidecar-start) |
Effect |
|---|---|---|---|
SIDECAR_PORT |
9100 |
9201 |
Listen port |
SIDECAR_HOST |
127.0.0.1 |
127.0.0.1 |
Bind address (DEV_MODE=true → 0.0.0.0 if host unset) |
SIDECAR_URL |
— | — | Client base URL (Python default http://127.0.0.1:9100) |
PI_SIDECAR_LOG_LEVEL |
info |
info |
Server / client log level |
Tip: Point the client at the port you actually started. Dev script uses 9201; the Node server and Python client default to 9100.
export SIDECAR_URL=http://127.0.0.1:9201 # when using pi-sidecar-start
3. Wait until healthy
curl -s http://127.0.0.1:9100/health
status |
Meaning |
|---|---|
ok |
Ready (model discovery finished) |
starting |
Discovery still running (HTTP 503) |
degraded |
Discovery failed; sessions may still work for already-known models |
from pi_sidecar_client import check_sidecar_available
ok, msg = await check_sidecar_available()
print(ok, msg)
4. Discover models
from pi_sidecar_client import list_models
all_models = await list_models()
gemini = await list_models(provider="gemini")
Friendly Python provider aliases map to sidecar ids:
| Client alias | Sidecar provider |
|---|---|
gemini |
google |
cursor |
acpx-cursor (model prefixed with cursor: if needed) |
claude |
google-vertex-claude |
Refresh after changing agents or auth:
curl -X POST http://127.0.0.1:9100/models/refresh
Diagnose a single provider:
from pi_sidecar_client import get_sidecar_client
client = get_sidecar_client()
status = await client.get_model_provider_status("google")
print(status["registered"], status["modelCount"])
Note: On non-loopback binds, auth fields on provider status are redacted. Prefer
127.0.0.1for local diagnostics. For Vertex auth setup, see Google Vertex Claude Provider.
5. Run a session
Single-shot (auto cleanup): use call_ai_once as in the Quick Example.
Multi-turn:
from pi_sidecar_client import call_ai, get_sidecar_client
result = await call_ai(
"I'm building a REST API in Python. What framework should I use?",
ai_provider="gemini",
ai_model="gemini-2.5-flash",
system_prompt="You are a senior Python developer. Be concise.",
)
session_id = result.session_id
result = await call_ai(
"Show me a minimal example with that framework.",
ai_provider="gemini",
ai_model="gemini-2.5-flash",
session_id=session_id,
)
await get_sidecar_client().delete_session(session_id)
Session lifecycle:
- Create session (
POST /sessions) with provider, model, system prompt, cwd. - Prompt (
POST /sessions/:id/prompt) — one in-flight prompt per session. - Optional abort (
POST /sessions/:id/abort). - Delete (
DELETE /sessions/:id) when finished.
Default built-in tools when you omit tools: read, grep, find, ls, bash.
cwd loads project resources from {cwd}/.pi/ and AGENTS.md. Optional agent_dir points at a global agent directory (e.g. ~/.pi/agent); it is rejected on non-loopback binds unless DEV_MODE=true (then ignored).
Advanced Usage
HTTP API surface
| Method | Path | Effect |
|---|---|---|
GET |
/health |
Readiness (ok / starting / degraded) |
GET |
/models |
List discovered models |
POST |
/models/refresh |
Re-run discovery |
GET |
/models/:provider/status |
Registration, model count, auth (redacted off-loopback) |
POST |
/sessions |
Create session → { session_id } |
POST |
/sessions/:id/prompt |
Send { "message": "..." } |
POST |
/sessions/:id/abort |
Cancel in-flight prompt |
DELETE |
/sessions/:id |
Destroy session |
Body size limit: 1 MiB. Concurrent prompts on the same session return conflict (busy).
Custom HTTP-backed tools
Pass custom_tools when creating a session. Entries with an http block are executed as outbound HTTP; {param} placeholders interpolate from tool args.
result = await call_ai_once(
"Look up user 42",
ai_provider="gemini",
ai_model="gemini-2.5-flash",
custom_tools=[{
"name": "fetch_data",
"description": "Fetch user data by ID",
"parameters": {
"type": "object",
"properties": {"userId": {"type": "string"}},
},
"http": {
"method": "GET",
"url": "https://api.example.com/users/{userId}",
},
}],
)
http field |
Type | Default | Effect |
|---|---|---|---|
method |
GET | POST | PUT | DELETE | PATCH |
(required) | HTTP method |
url |
string | (required) | URL template with {params} |
headers |
object | — | Header templates |
query_params / queryParams |
object | — | Query templates |
body_template / bodyTemplate |
object | string | — | Body template |
timeout_ms / timeoutMs |
number | 30000 |
Request timeout |
Restrict the built-in tool allowlist with tools=["read", "grep"] (or [] for none).
ACPX and CLI model sources
export ACPX_AGENTS=cursor
export CLI_AGENTS=claude,gemini
npx pi-sidecar
Then use providers such as acpx-cursor / cli-claude (or Python aliases cursor / mapped names). Refresh with POST /models/refresh after changing env. See Configuration & Settings for project-level agent lists used by interactive Pi.
Watchdog (companion process)
export SIDECAR_WATCHDOG_URL=http://127.0.0.1:8000/health
npx pi-sidecar
When set, the sidecar polls that URL and shuts itself down after consecutive failures (default: 60s grace, 30s interval, 10s timeout, 6 failures).
Docker / process coupling
Start the sidecar, wait for /health, then start your app and trap cleanup — see the package entrypoint.example.sh pattern (SIDECAR_PORT default 9100).
Using vs extending
| Goal | What you do |
|---|---|
| Use | Run pi-sidecar, call call_ai_once / SidecarClient, pass custom_tools / tools |
| Extend | Call startSidecar({ port, host, watchdogUrl, watchdogOptions }) from Node, or override extension paths with SIDECAR_ACPX_EXTENSION_PATH, SIDECAR_CLI_PROVIDER_EXTENSION_PATH, SIDECAR_PROVIDER_EXTENSION_PATH, SIDECAR_VERTEX_EXTENSION_PATH, SIDECAR_SUBAGENT_EXTENSION_PATH |
Startup lifecycle:
- Assert Pi SDK version ≥ 0.81.1.
- Listen on host/port.
- Optionally start watchdog.
- Async model discovery —
/healthisstartinguntil ready. - Serve session/prompt routes; idle sessions cleaned after 1 hour.
Parallel calls and usage hooks
from pi_sidecar_client import run_parallel_with_limit, set_usage_recorder
set_usage_recorder(my_recorder) # sync or async; kwargs: request_id, result, call_type, ...
results = await run_parallel_with_limit(tasks, max_concurrency=5)
ai_call_timeout on call_ai / call_ai_once is in minutes (converted to HTTP timeout seconds).
Troubleshooting
| Problem | Fix |
|---|---|
| Client connection errors | Align SIDECAR_URL with the server port (9100 vs 9201). |
/health stuck on starting / 503 |
Wait for discovery; check logs (PI_SIDECAR_LOG_LEVEL=debug). |
| Empty model list | Auth providers; set ACPX_AGENTS / CLI_AGENTS; POST /models/refresh; check GET /models/:provider/status. |
| Session create rejects model | Use an id from GET /models; interactive OAuth-only providers are blocked in this headless service. |
409 / session busy |
Wait for the current prompt or POST .../abort. |
agent_dir rejected |
Bind on loopback, or use DEV_MODE=true (value discarded). |
| Pi version error at startup | Upgrade @earendil-works/pi-coding-agent to ≥ 0.81.1. |
Warning: Default bind is loopback-only. Binding to
0.0.0.0exposes an unauthenticated AI API on the network — use only behind a trusted boundary.