Live proof showed the previous OAuth catalog default was rejected at session start. Switch the catalog, docs, fixtures, and regenerated schema to the working default while keeping provider config generic.
16 KiB
Paseo Agent provider
Paseo Agent is the built-in agent provider that runs Pi's coding-agent harness in
process (no pi CLI, no ~/.pi discovery). Its provider id is paseo and its
model backends are configured under agents.paseo in $PASEO_HOME/config.json.
Use it like any other agent provider:
paseo run --provider paseo --model <providerInstance>/<modelId> "Reply with pong"
Example: --model openrouter/openai/gpt-4o-mini selects the openrouter provider
instance and the openai/gpt-4o-mini model exposed by that instance.
Smoke note: the daemon supervisor runs from
packages/server/dist. After changing provider/config code, runnpm run build:server(or run a source/dev daemon) before smoking, otherwise staledistmay reject or omitagents.paseobehavior. Always pass--host <addr>to CLI smoke commands so they hit your isolated daemon, not the real daemon on:6767.
Provider catalog
Paseo Agent model providers are catalog-driven. catalog.ts is the source of truth for
which provider types the daemon knows how to configure. A catalog entry is data only:
id, label, wire API, base URL, optional headers, auth metadata, and optional default
models. The app picker, CLI setup, config RPCs, runtime provider resolution, and auth
state all use that same data.
The current catalog contains four entries:
| id | label | api | base URL | auth |
|---|---|---|---|---|
openrouter |
OpenRouter | openai-completions |
https://openrouter.ai/api/v1 |
API key, OPENROUTER_API_KEY |
chatgpt |
ChatGPT | openai-codex-responses |
https://chatgpt.com/backend-api |
OAuth, openai-codex flow |
kimi |
Kimi Coding Plan | anthropic-messages |
https://api.kimi.com/coding |
API key, KIMI_API_KEY |
opencode-go |
OpenCode Go | openai-completions |
https://opencode.ai/zen/go/v1 |
API key, OPENCODE_API_KEY |
Only chatgpt ships with a default model (gpt-5.4-mini). API-key providers currently
require at least one explicit --model or options.models[] entry.
Config shape
agents.paseo.providers is structurally generic. Provider instance names are free-form
keys; provider types are catalog ids. That means you can use the catalog id as the
default instance name, or create several instances of the same type with different
models, keys, or base URLs.
{
"agents": {
"paseo": {
"defaultAgent": "orchestrator",
"defaultModel": "openrouter-main/openai/gpt-4o-mini",
"providers": {
"openrouter-main": {
"type": "openrouter",
"options": {
"apiKey": "$OPENROUTER_API_KEY",
"models": [
{ "id": "openai/gpt-4o-mini", "label": "GPT-4o mini" },
{ "id": "anthropic/claude-3.7-sonnet", "reasoning": true },
],
},
},
"chatgpt": {
"type": "chatgpt",
"options": {
"models": [{ "id": "gpt-5.4-mini", "reasoning": true }],
},
},
"kimi": {
"type": "kimi",
"options": {
"apiKey": "$KIMI_API_KEY",
"models": [{ "id": "k2p7" }],
},
},
},
},
},
}
The provider entry shape is:
{
"type": "openrouter",
"options": {
"apiKey": "$OPENROUTER_API_KEY",
"baseUrl": "https://...",
"api": "openai-completions",
"headers": { "X-Example": "value" },
"authHeader": true,
"refreshToken": "$CHATGPT_REFRESH_TOKEN",
"models": [
{
"id": "provider/model-id",
"label": "Readable label",
"api": "anthropic-messages",
"reasoning": true,
"contextWindow": 128000,
"maxTokens": 16384,
},
],
},
}
Most options are overrides for the catalog entry:
apiKeymay be omitted for API-key providers. Omitted means "use the catalog env var" such asOPENROUTER_API_KEY.apiKeymay also be a literal key,$ENV,${ENV}, or!commandexpression. Paseo mirrors Pi's config-value semantics: literals and commands count as configured; env references count only when every referenced env var is set in the daemon environment.baseUrl,api,headers, andauthHeaderoverride or extend catalog defaults.models[]exposes the model ids usable as<providerInstance>/<modelId>. A model may overrideapiwhen a single backend serves mixed protocols.refreshTokenis an advanced OAuth seed path. Prefer the OAuth store described below.
Env references make config portable: config.json can be copied between machines while
the secret stays in that machine's daemon environment or keychain command.
defaultModel is optional and uses the same <providerInstance>/<modelId> form. An
explicit session model wins, then the selected agent definition's model, then
agents.paseo.defaultModel, then Pi's first available model.
defaultProfile is still accepted as a legacy alias for defaultAgent.
Authentication
API-key providers use the catalog auth metadata plus the configured apiKey expression.
Redacted provider responses include an optional auth state:
Connectedmeans the key or credential is configured and at least one model is exposed.Needs attentionmeans the instance exists but the auth expression does not currently resolve, the OAuth store binding does not match, or another auth precondition is missing.not configuredis used by older/no-auth responses.
Secrets are not returned in catalog responses, redacted provider responses, or CLI table output.
OAuth store
OAuth credentials live in Paseo's store, not in another tool's auth file:
$PASEO_HOME/paseo-agent/auth.json
The store is created through Pi's AuthStorage. The parent directory is private and the
file is written mode 0600; Pi also re-chmods on write. During a Paseo Agent session,
Pi reads the credential, refreshes expired access tokens, and persists refresh-token
rotation back into the same Paseo-owned file.
Stored OAuth credentials are bound to the provider instance's { flow, baseUrl }. If the
catalog flow or configured base URL changes, the old credential is left on disk but the
provider reports Needs attention until it is authorized again. This prevents a token
for one OAuth target from silently being used against another.
The OAuth implementation uses Pi's OAuth registry. Catalog OAuth entries are limited to
flows that registry knows about: openai-codex, anthropic, and github-copilot.
The current catalog only uses openai-codex for chatgpt.
CLI setup
The provider CLI talks to the selected daemon. Always pass --host when smoking an
isolated daemon.
Actual help text:
Usage: paseo provider add [options] [id]
Configure a Paseo Agent model provider
Arguments:
id Catalog provider id; omit to choose interactively
Options:
--name <instanceName> Provider instance name (default: provider id)
--model <id> Model ID to expose (repeatable, comma-separated;
defaults to catalog models) (default: [])
--api-key-stdin Read API key from stdin
--device-code Use daemon-run device-code OAuth instead of browser
OAuth
--json Output in JSON format
--host <host> Daemon host target: host:port or
tcp://host:port?ssl=true&password=secret (default:
local socket/pipe, then localhost:6767)
-h, --help display help for command
Usage: paseo provider ls [options]
List configured Paseo Agent model providers
Options:
--json Output in JSON format
--host <host> Daemon host target: host:port or
tcp://host:port?ssl=true&password=secret (default: local
socket/pipe, then localhost:6767)
-h, --help display help for command
Usage: paseo provider rm [options] <name>
Remove a Paseo Agent model provider
Arguments:
name Provider instance name
Options:
--json Output in JSON format
--host <host> Daemon host target: host:port or
tcp://host:port?ssl=true&password=secret (default: local
socket/pipe, then localhost:6767)
-h, --help display help for command
Examples:
printf '%s\n' "$OPENROUTER_API_KEY" |
paseo provider add \
openrouter \
--api-key-stdin \
--model openai/gpt-4o-mini \
--host 127.0.0.1:7911
paseo provider add kimi \
--model k2p7 \
--host 127.0.0.1:7911
# Press Enter at the API-key prompt to store "$KIMI_API_KEY".
paseo provider add chatgpt --device-code --host 127.0.0.1:7911
Without an [id], provider add prints the catalog and prompts for a selection. For
API-key providers it writes the configured key expression into config.json. For OAuth
providers it first writes the provider config, then stores the OAuth credential in the
daemon's Paseo-owned auth store.
provider add is idempotent for the same instance name: running it again updates the
existing entry instead of creating a duplicate. provider rm <name> removes only that
provider instance and clears defaultModel if it pointed at the removed instance.
App setup
The app uses the same catalog and config RPCs as the CLI. In Settings, open the host
settings, then the Paseo Agent provider section. The app fetches the catalog from the
connected daemon, shows catalog entries in the picker, and renders either an API-key
form or an OAuth sign-in flow from the entry's auth.kind.
Configured rows show the redacted provider state from the daemon: label, provider type,
models, availability, and auth state. The app gates this UI on
server_info.features.paseoAgentCatalog; older daemons show an update-host affordance
instead of trying to synthesize the feature through older RPCs.
Wire surface
The catalog surface is gated by:
server_info.features.paseoAgentCatalog
RPC names use dotted namespaces:
| request | response |
|---|---|
config.paseo_agent.get_catalog.request |
config.paseo_agent.get_catalog.response |
config.paseo_agent.get_providers.request |
config.paseo_agent.get_providers.response |
config.paseo_agent.set_provider.request |
config.paseo_agent.set_provider.response |
config.paseo_agent.remove_provider.request |
config.paseo_agent.remove_provider.response |
config.paseo_agent.oauth.start.request |
config.paseo_agent.oauth.start.response |
config.paseo_agent.oauth.complete.request |
config.paseo_agent.oauth.complete.response |
config.paseo_agent.oauth.store_credential.request |
config.paseo_agent.oauth.store_credential.response |
Protocol strings are intentionally open. Provider ids, auth kinds, OAuth flow names, and wire API names are strings, not closed enums. Old clients can parse new catalog entries, and new clients can show "update host" only when the daemon lacks the catalog feature.
Adding a catalog entry
Adding a new model-provider type should be a data change:
- Add one entry to
PASEO_AGENT_PROVIDER_CATALOGinpackages/server/src/server/agent/providers/paseo-agent/catalog.ts. - Set
id,label,api,baseUrl,auth, and any provider-levelheaders. - Add default
models[]only when there is a safe, broadly usable default. Otherwise leave it empty so CLI/app users must choose a model id. - Use
auth: { kind: "api_key", envVar: "..." }for key-backed providers. - Use
auth: { kind: "oauth", flow: "..." }only for flows supported by Pi's OAuth registry (openai-codex,anthropic,github-copilot). - Add or update focused tests around catalog copying, provider resolution, auth state, and CLI/app rendering if the new entry exercises a new shape.
Do not add provider-specific branches in the runtime, CLI, or app. The catalog entry is what unlocks CLI setup, app setup, redacted provider state, config persistence, and model addressing.
MCP tools
Paseo Agent bridges AgentSessionConfig.mcpServers into Pi custom tools, so the
daemon-injected paseo MCP server (and any other configured MCP server) is available to
the model. On session start the provider connects to each server, lists its tools, and
registers them as Pi tools named <serverName>__<toolName>; tool input schemas (JSON
Schema) are converted to TypeBox, calls are proxied to the MCP server, and results map
back to the model. Connections are torn down on session close. Servers that fail to
connect or list are logged and skipped rather than failing the session.
Transports: HTTP (streamable) is the primary path (the injected paseo server is HTTP);
SSE and stdio transports are also wired via the MCP SDK. No extra config is needed: MCP
servers come from Paseo's normal injection/config, not from agents.paseo.
Agent definitions
Paseo Agent can load a Paseo-owned agent definition from $PASEO_HOME/agents/*.md.
Configure the default agent in agents.paseo.defaultAgent; orchestrator resolves
to $PASEO_HOME/agents/orchestrator.md. Only top-level markdown files are selectable
agents. Reusable partials can live anywhere under $PASEO_HOME/agents.
{
"agents": {
"paseo": {
"defaultAgent": "orchestrator",
"defaultModel": "openrouter-main/openai/gpt-4o-mini",
"providers": {},
},
},
}
Example $PASEO_HOME/agents/orchestrator.md:
---
name: Orchestrator
description: Coordinates work through Paseo-managed agents
prompt: extend
mcp: [paseo]
model: openrouter-main/openai/gpt-4o-mini
tools: [read, grep, paseo__list_agents, paseo__create_agent]
permissions:
- tool: paseo__archive_*
action: deny
---
!{{./partials/collaboration.md}}
Use the Paseo MCP tools to inspect active agents, create focused helper agents, and
summarize handoffs clearly.
!{{./partials/review-rules.md}}
prompt: extend keeps Pi's default base prompt and prepends the composed agent body to
the append list. prompt: override uses the agent body as the custom base prompt, so
Pi's default base prompt is skipped. In both prompt modes, per-session systemPrompt is
appended after the agent, and the daemon-level append prompt is appended last.
Frontmatter supports name, description, prompt, mcp, model, tools,
permissions, and projectContext. projectContext is parsed for a future explicit
project-context model, but it does not activate implicit AGENTS.md/CLAUDE.md
discovery; Paseo Agent still keeps Pi context discovery off. model is an agent default:
an explicit session model wins, then the selected agent model, then
agents.paseo.defaultModel, then Pi's first available model.
Partials use bang braces and expand exactly where they appear: !{{./partials/base.md}}.
Paths are relative to the file containing the directive and are confined to
$PASEO_HOME/agents: absolute paths, directory escapes, cycles, overly deep partial
chains, oversized definitions, and frontmatter inside partials are rejected.
tools is the Pi tool allowlist for the agent: it controls what the model sees and can
call. Omit it to use Pi's default built-in tools plus bridged MCP tools. permissions is
an ordered first-match policy for active tool calls. The first matching tool pattern
wins; unmatched tools are allowed. Denied calls are blocked before execution through Pi's
tool preflight hook, so the policy applies to built-in, custom, and bridged MCP tools.
mcp: [paseo] is an expectation check, not a new injection mechanism. The normal daemon
MCP injection still supplies the actual server; if an agent declares an MCP server that
is not present in the session's mcpServers, Paseo Agent logs a warning and continues.