This document describes the application configuration file used by the agent server. It is based on the runtime schema in packages/agent-server/src/config.ts and the example at data/config.example.json.
- Source files
- Environment variables
- Application configuration (config.json)
- Security Notes
- Full Example
packages/agent-server/src/config.tsdata/config.example.json
Pi SDK chat uses provider-specific environment variables. Common examples include:
- OpenAI:
OPENAI_API_KEY - Anthropic:
ANTHROPIC_OAUTH_TOKEN(preferred) orANTHROPIC_API_KEY - Google Gemini:
GEMINI_API_KEY - Groq:
GROQ_API_KEY - Mistral:
MISTRAL_API_KEY - OpenRouter:
OPENROUTER_API_KEY - XAI:
XAI_API_KEY - Cerebras:
CEREBRAS_API_KEY - Minimax:
MINIMAX_API_KEY - Minimax (CN):
MINIMAX_CN_API_KEY - ZAI:
ZAI_API_KEY - Vercel AI Gateway:
AI_GATEWAY_API_KEY - OpenCode:
OPENCODE_API_KEY - Azure OpenAI:
AZURE_OPENAI_API_KEY(withAZURE_OPENAI_BASE_URL/AZURE_OPENAI_RESOURCE_NAME)
The assistant does not resolve these itself; it passes requests to the Pi SDK, which
reads the provider environment variables directly. For a complete list, see the
@earendil-works/pi-ai README.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP/WebSocket server port |
DATA_DIR |
./data |
Directory for session data (event logs, preferences, plugin settings) |
APP_CONFIG_PATH |
- | Override config file location |
| Variable | Default | Description |
|---|---|---|
TTS_BACKEND |
openai |
TTS backend: openai or elevenlabs |
OPENAI_TTS_MODEL |
gpt-4o-mini-tts |
OpenAI TTS model |
TTS_VOICE |
alloy |
Voice name for TTS output |
TTS_FRAME_DURATION_MS |
250 |
PCM frame duration for TTS output; larger values reduce client scheduling overhead |
AUDIO_OUTPUT_SPEED |
- | Playback speed multiplier (e.g., 1.2) |
OpenAI TTS requires OPENAI_API_KEY.
Realtime duplex voice reuses the same server-side OpenAI credential as TTS/chat:
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
- | Required for Realtime. Held only on the agent-server host; never sent to Android/web clients. |
OPENAI_REALTIME_MODEL |
gpt-realtime-2.1 |
Pinned Realtime model id (matches T3). |
OPENAI_REALTIME_VOICE |
marin |
OpenAI Realtime output voice (matches T3 default; female). |
Storage plan: set OPENAI_API_KEY in the agent-server environment (systemd unit, shell profile, or secrets manager that injects env). Do not put the raw key in client apps, Capacitor assets, or committed config.json. Optional ${OPENAI_API_KEY} substitution in config.json is fine for other agent fields; Realtime reads the env via loadEnvConfig() the same way OpenAI TTS does.
Capability probe: GET /api/voice/capabilities reports agentRealtime.status as ready or not-configured.
Realtime tool exposure is explicit opt-in. Configure under voice.realtime:
{
"voice": {
"realtime": {
"toolAllowlist": [
"interaction_end",
"lists_*",
"web_search",
"codex_threads_list",
"codex_threads_find",
"codex_threads_status",
"codex_threads_messages"
],
"toolDenylist": []
}
}
}| Field | Description |
|---|---|
toolAllowlist |
Glob patterns for tool names available on Realtime sessions. Missing or empty ⇒ no tools. Add interaction_end to let the voice agent end its call. |
toolDenylist |
Optional globs removed after allowlist matching. |
instructions |
Optional system-instruction override for the Realtime model. |
Text agents use per-agent toolAllowlist / toolDenylist independently of this block.
interaction_end gives text and Realtime agents one shared way to honor requests such as
“we can stop now.” Add it to each text agent's toolAllowlist and to
voice.realtime.toolAllowlist where the behavior is wanted.
- Tell agents they must call the tool when the user asks to stop. A textual or spoken acknowledgment alone does not end the interaction.
- In a text-agent turn, the tool returns normally so the agent can complete its final response. Android correlates the tool call with that response and suppresses Auto Listen for that turn.
- In a Realtime session, the same tool ends the live call immediately. The voice agent should say any brief goodbye before calling it.
- The optional
reasonargument is a short diagnostic string such asuser_requestordone.
web_search is a built-in tool (not a plugin). It runs the local Grok CLI headless for live public web and X research.
| Item | Detail |
|---|---|
| Parameters | query (required natural-language question), continue (optional bool to resume prior Grok session for this conversation) |
| Enable for text agents | Add web_search to the agent’s toolAllowlist |
| Enable for Realtime | Add web_search to voice.realtime.toolAllowlist |
| Host prerequisites | grok on PATH (or ASSISTANT_GROK_BIN); Grok auth (~/.grok/auth.json and/or XAI_API_KEY); do not lock off bypassPermissions in Grok requirements; avoid untrusted Grok MCP servers for the service user |
| Not the same as | Plugin search_* tools (in-app notes/lists search) |
Design: docs/design/web-search-tool.md.
The codex_threads_* built-in tools use the local codex-threads CLI to inspect
and interact with a configured Codex app-server. They are individually
allowlisted for text agents and Realtime voice.
| Tool | Purpose |
|---|---|
codex_threads_list |
List bounded recently updated threads, including cwd/project and status. |
codex_threads_find |
Match a bounded recent candidate set by name, preview, and cwd metadata. It is not full transcript search. |
codex_threads_status |
Load and inspect one thread and its active-turn state. |
codex_threads_messages |
Read a bounded recent user/assistant transcript. |
codex_threads_send |
Start or queue a turn; optionally wait up to 120 seconds for bounded final text. |
codex_threads_steer |
Add guidance to the exact currently active turn. |
codex_threads_create |
Create, optionally name, and start a thread in an allowed cwd. |
codex_threads_rename |
Set a non-empty thread name. |
The Realtime example exposes only read operations. Add write operation names explicitly when voice-driven mutations are desired; Realtime does not apply per-agent capability rules.
Used when TTS_BACKEND=elevenlabs.
| Variable | Default | Description |
|---|---|---|
ELEVENLABS_API_KEY |
- | ElevenLabs API key (required) |
ELEVENLABS_TTS_VOICE_ID |
- | ElevenLabs voice ID (required) |
ELEVENLABS_TTS_MODEL |
eleven_multilingual_v2 |
ElevenLabs model ID |
ELEVENLABS_TTS_BASE_URL |
https://api.elevenlabs.io |
API base URL |
| Variable | Default | Description |
|---|---|---|
MCP_TOOLS_ENABLED |
auto | Explicit enable/disable for external MCP servers (true/1 or false/0) |
External MCP servers are configured in config.json under mcpServers.
Per session, 1-minute sliding window.
| Variable | Default | Description |
|---|---|---|
MAX_MESSAGES_PER_MINUTE |
60 |
Max client messages per minute |
MAX_AUDIO_BYTES_PER_MINUTE |
2000000 |
Max audio bytes per minute |
MAX_TOOL_CALLS_PER_MINUTE |
30 |
Max tool calls per minute |
| Variable | Default | Description |
|---|---|---|
AUDIO_SAMPLE_RATE |
24000 |
Audio sample rate in Hz |
AUDIO_INPUT_MODE |
manual |
Input mode: server_vad or manual |
AUDIO_TRANSCRIPTION_ENABLED |
false |
Forward transcription events to client |
| Variable | Default | Description |
|---|---|---|
DEBUG_CHAT_COMPLETIONS |
false |
Log Pi SDK request/response payloads (tools included, auth redacted) |
DEBUG_HTTP_REQUESTS |
false |
Log HTTP request details |
The agent server loads configuration from:
${DATA_DIR}/config.jsonby defaultAPP_CONFIG_PATHwhen set (absolute or relative path)
Environment defaults such as DATA_DIR are described above.
config.json supports ${VARNAME} substitution in all string values. This is useful for:
- Paths that depend on home directory:
"workspaceRoot": "${HOME}/workspaces" - API keys and secrets:
"apiKey": "${LOCAL_LLM_KEY}" - Runtime-specific paths:
"socketPath": "${XDG_RUNTIME_DIR}/podman/podman.sock"
If the environment variable is missing, the placeholder is replaced with an empty string.
Examples:
{
"plugins": {
"coding": {
"enabled": true,
"local": {
"workspaceRoot": "${HOME}/coding-workspaces"
}
}
},
"agents": [
{
"agentId": "claude-cli",
"displayName": "Claude",
"description": "Claude via CLI",
"chat": {
"provider": "claude-cli",
"config": {
"workdir": "${HOME}/projects",
"wrapper": {
"path": "${HOME}/bin/claude-wrapper.sh"
}
}
}
}
],
"mcpServers": [
{
"command": "${HOME}/bin/mcp-server",
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
]
}| Key | Type | Description |
|---|---|---|
sessions |
object | Session cache settings. |
agents |
array | Agent persona definitions and chat provider config. |
profiles |
array | Shared profile (instance) definitions for cross-plugin scoping. |
plugins |
object | Plugin enablement and per-plugin config. |
mcpServers |
array | External MCP servers launched over stdio. |
codexThreads |
object | Host configuration for built-in codex_threads_* tools. |
Controls session cache behavior.
{ "sessions": { "maxCached": 100 } }maxCached: maximum number of sessions cached in memory
{
"codexThreads": {
"allowedServers": ["main", "work"],
"binary": "codex-threads",
"permissionMode": "app-server-default",
"allowedCwdRoots": ["/home/kevin/worktrees"]
}
}| Field | Default | Description |
|---|---|---|
allowedServers |
required | Nonempty list of aliases from the host user's codex-threads config. Every tool call must select one of these aliases through its required server argument. |
binary |
codex-threads |
Executable name or trusted host-configured path. |
permissionMode |
app-server-default |
app-server-default passes --no-yolo; full-access deliberately uses the CLI's approval-policy-never/full-access behavior. Agents cannot override it. |
allowedCwdRoots |
[] |
Absolute roots permitted for codex_threads_create. With no roots, create is unavailable while other operations still work. |
The host service user must have a working codex-threads configuration and a
reachable app-server for each allowed alias. Every operation requires a
model-visible server argument constrained to allowedServers. Calls use JSON output,
argument-array subprocess execution, hard time/output limits, and compact
result projections. Full-text CLI search is intentionally not exposed because
its time filtering can scan unbounded history; codex_threads_find searches a
fixed recent metadata candidate set instead.
codex_threads_messages accepts an optional exact turnId returned by
codex_threads_send. When present, the server scans the bounded recent-turn
window, filters to that turn, and only then applies the final message limit.
This lets asynchronous relay agents retrieve the response for the turn they
submitted without confusing it with newer thread activity.
Text agents opt in through toolAllowlist and may additionally scope
codex_threads.read versus codex_threads.write capabilities. Realtime opts in
independently through exact names or globs under voice.realtime.toolAllowlist.
Defines agent personas and chat providers.
{
"agents": [
{
"agentId": "general",
"displayName": "General Assistant",
"description": "A helpful assistant.",
"systemPrompt": "You are a helpful assistant.",
"toolAllowlist": ["*"],
"chat": {
"provider": "pi"
}
}
]
}See the Agents section below for the full schema and provider-specific config.
Defines shared profile identifiers that can be reused across plugin instances. Instance ids must
match an entry in this list (the built-in default profile is always available).
{
"profiles": [
{ "id": "default", "label": "Global" },
{ "id": "work", "label": "Work" },
{ "id": "personal", "label": "Personal" }
]
}Use these ids in plugin instance configuration:
{
"profiles": [{ "id": "default" }, { "id": "work" }],
"plugins": {
"notes": { "enabled": true, "instances": ["default", "work"] }
}
}Enable or configure server plugins.
{
"plugins": {
"sessions": { "enabled": true },
"agents": { "enabled": true },
"scheduled-sessions": { "enabled": true },
"lists": { "enabled": true },
"notes": { "enabled": true },
"questions": { "enabled": true },
"url-fetch": { "enabled": true }
}
}Many plugins accept additional settings. See each plugin README for details.
Plugins that support multiple data instances can also define instances:
{
"plugins": {
"time-tracker": {
"enabled": true,
"instances": ["work", { "id": "personal", "label": "Personal" }]
}
}
}- Instance entries can be strings or objects. Object entries may include
labeland plugin-specific overrides (either inline or underconfig):
{
"plugins": {
"notes": {
"enabled": true,
"instances": [
"work",
{ "id": "personal", "label": "Personal" },
{ "id": "shared", "config": { "label": "Shared" } }
]
}
}
}- Instance ids are lowercased slugs (
[a-z0-9_-]). - The default instance id is always
defaultand cannot be renamed or removed.
Plugins can opt into automatic git snapshots of their data directories using gitVersioning:
{
"plugins": {
"notes": {
"enabled": true,
"gitVersioning": {
"enabled": true,
"intervalMinutes": 5
}
}
}
}- One git repository is created per plugin instance directory.
- Snapshots are committed on the configured interval and use a local "AI Assistant" git author.
- Requires
gitavailable on the serverPATH.
Plugins that execute code (like the coding plugin) run locally. local.workspaceRoot may use the
session-scoped macro ${session.workingDir} so relative file paths and bash commands anchor to
the session picker’s working directory.
{
"plugins": {
"coding": {
"enabled": true,
"mode": "local",
"local": {
"workspaceRoot": "/path/to/workspaces"
}
}
}
}| Field | Type | Description |
|---|---|---|
mode |
string | Execution mode. Only local is supported. |
local.workspaceRoot |
string | Root directory for local workspaces. ${session.workingDir} resolves from attributes.core.workingDir for interactive tool calls. |
Notes:
- Coding tools now execute directly through the imported
@earendil-works/pi-coding-agentlocal tool implementations.
Agent coordination is provided by the agents plugin. Enable it in config.json and use the generated tools:
| Tool | Description |
|---|---|
agents_message |
Send a message to another agent (sync or async) without switching sessions. |
agents_list |
List configured agents for delegation or messaging. |
Terminology: users may say "agent" to refer to a configured assistant persona (for example, "journal agent" or "todo agent").
Defines external MCP tool servers (Model Context Protocol) launched over stdio.
{
"mcpServers": [
{
"name": "github",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
]
}name: optional display namecommand: executable to launchargs: optional arguments listenv: optional environment map (supports${ENV}substitution)
{
"agentId": "notes",
"displayName": "Notes",
"description": "Manages notes.",
"systemPrompt": "You are a notes assistant.",
"type": "chat",
"toolAllowlist": ["notes_*"],
"toolDenylist": [],
"toolApprovals": {
"required": ["bash", "write", "edit"]
},
"toolExposure": "skills",
"skillAllowlist": ["notes"],
"sessionWorkingDir": {
"mode": "prompt",
"roots": ["/home/kevin/worktrees"]
},
"skills": [
{ "root": "~/skills", "available": ["*"], "inline": ["my-critical-*"] },
{ "root": "worktrees/assistant/skills" }
],
"capabilityAllowlist": ["*"],
"agentAllowlist": ["*"],
"uiVisible": true,
"apiExposed": false
}| Field | Type | Description |
|---|---|---|
agentId |
string | Unique id (used by tools and session routing). |
displayName |
string | UI label. |
description |
string | UI description and prompt context. |
type |
string | Agent type: chat (default) or external. |
systemPrompt |
string | Optional custom prompt. |
toolAllowlist |
array | Glob patterns for tool access. |
toolDenylist |
array | Glob patterns for tool denylist. |
toolApprovals |
object | Native Pi SDK tool approval policy. required contains tool-name glob patterns that must receive an interactive Approve response before execution. |
toolExposure |
string | tools, skills, or mixed. |
skillAllowlist |
array | Plugin ids exposed as CLI skills. |
skillDenylist |
array | Plugin ids blocked from skill exposure. |
skills |
array | Instruction skills (filesystem SKILL.md discovery + Pi-style prompt inclusion). |
capabilityAllowlist |
array | Glob patterns for capability access. |
capabilityDenylist |
array | Glob patterns for capability denylist. |
agentAllowlist |
array | Glob patterns for agents this agent can delegate to. |
agentDenylist |
array | Glob patterns for agents blocked from delegation. |
sessionWorkingDir |
object | Optional working-directory policy for new sessions. Use { "mode": "fixed", "path": "/abs/path" }, { "mode": "prompt", "roots": ["/abs/root"] }, or { "mode": "none" } to leave core.workingDir unset by default. |
uiVisible |
boolean | Hide from built-in UI if false. |
apiExposed |
boolean | Reserved for external API tools (currently unused). |
toolApprovals.required accepts the same exact-name and * glob patterns used by the tool
allowlist. Every matching native Pi SDK tool call pauses before execution and appears above the
chat composer with Deny and Approve actions. For example, protect filesystem reads and
writes as well as shell commands:
{
"toolApprovals": {
"required": ["bash", "read", "write", "edit", "find", "grep"]
}
}The policy is enforced only for the native pi provider, whose tools execute through the
Assistant tool host. Configuration is rejected for CLI providers because their internal tool
execution cannot be intercepted by Assistant. Approval fails closed: if no connected client has
interactive tool prompts enabled, the matching tool returns an interaction_unavailable error
without running.
When sessionWorkingDir is set to { "mode": "prompt", "roots": [...] }, the UI shows a
working directory picker for new sessions created with that agent. The options are the
immediate subdirectories of each configured root (all of which must be absolute paths).
When sessionWorkingDir is set to { "mode": "fixed", "path": "/abs/path" }, new sessions
for that agent automatically use the fixed directory without prompting.
When sessionWorkingDir is set to { "mode": "none" }, new sessions do not get a default
working directory from the agent. This is equivalent to omitting the field and makes the
intent explicit in config.
The selected or fixed directory is stored in attributes.core.workingDir and can be consumed
by other systems such as ${session.workingDir} CLI macros and the coding plugin's local
workspace configuration.
The skills field is for instruction skills discovered from SKILL.md files on disk and injected
into the agent's system prompt. This is separate from plugin/manifest-driven “CLI skills” controlled by
toolExposure + skillAllowlist.
Each entry is a “source”:
root: directory to recursively scan forSKILL.md.available: glob patterns over skillnameto include in<available_skills>.inline: glob patterns over skillnameto inline as<skill name="...">...</skill>.- If both
availableandinlineare omitted for a source, it defaults toavailable: ["*"].
The contextFiles field injects ordinary UTF-8 text files into the agent's system prompt after
instruction skills. This is intended for static project context that should be loaded once at
startup and reused for later prompt rebuilds.
Each entry is a rooted source:
{
"contextFiles": [
{
"root": "./context",
"include": ["README.md", "product/overview.md", "product/policies/*.md", "shared/**/*.md"],
},
],
}root: directory to search within. Relative roots resolve against the config file directory when the config is loaded.include: root-relative file paths or glob patterns. Matches are ordered by source order, then include-pattern order, then lexical path order within each pattern.- Matches are restricted to files inside the declared root, even when symlinks are involved.
- Duplicate matches are kept only on the first match.
- Startup fails if a root is missing, unreadable, outside-root traversal is detected, an include pattern matches nothing, or a matched file is binary / invalid UTF-8.
- File contents are cached at startup. Changes on disk are not picked up until restart.
When type is external, the agent forwards messages to an external HTTP endpoint instead of
using a local chat provider. This is useful for integrating with external AI services or
custom agent implementations.
{
"agentId": "external-example",
"displayName": "External Agent",
"description": "An external agent implementation.",
"type": "external",
"external": {
"inputUrl": "http://external.internal/v1/assistant/input",
"callbackBaseUrl": "http://agent-server.internal"
}
}| Field | Type | Description |
|---|---|---|
external.inputUrl |
string | URL where messages are POSTed to the external agent. |
external.callbackBaseUrl |
string | Base URL the external agent uses to call back with responses. |
External agents receive messages via POST and respond asynchronously via callbacks. See
docs/design/external-agents.md for the full protocol specification.
{
"chat": {
"provider": "claude-cli",
"config": {
"workdir": "/path/to/workspace",
"extraArgs": ["--model", "sonnet"]
}
}
}Supported providers:
pi(default)claude-clicodex-clipi-cli
| Provider | CLI Tool | Description |
|---|---|---|
pi |
- | Pi SDK in-process chat (upstream providers configured via Pi). |
claude-cli |
claude |
Anthropic Claude CLI with tool use. |
codex-cli |
codex |
OpenAI Codex CLI with file editing and shell. |
pi-cli |
pi |
Pi CLI agent. |
{
"agents": [
{
"agentId": "claude-cli",
"displayName": "Claude",
"description": "Claude via CLI",
"chat": {
"provider": "claude-cli",
"config": {
"workdir": "/path/to/workspace",
"extraArgs": ["--model", "sonnet", "--dangerously-skip-permissions"]
}
}
}
]
}Scheduled sessions are no longer configured on agents. Use the scheduled-sessions plugin to
create and manage them dynamically. They are stored in the plugin data directory under
data/plugins/scheduled-sessions/default/schedules.json.
The same plugin also stores pending one-shot session wake-ups under
data/plugins/scheduled-sessions/default/wakeups.json. Wake-ups are limited to native Pi SDK
sessions (chat.provider: "pi"), target the current tool session for create/list/update/cancel
operations, and send the configured message back to that session at the requested time. If the
session is busy when the wake-up fires, the message is added to the existing per-session message
queue. A session can have up to 25 active wake-ups.
Global one-time reminders are stored alongside them under
data/plugins/scheduled-sessions/default/reminders.json. Reminders are not session-scoped and do
not start an agent turn. When due, they create an ordinary durable notification titled Reminder
with the reminder text as its body and TTS content, using one-way speak playback when standalone
notification playback is enabled. The Notifications plugin must be enabled before creating a
reminder. A maximum of 25 reminders can be pending globally.
Each persisted schedule record includes:
agentIdscheduleIdcron- optional
prompt - optional
preCheck - optional
sessionTitle - optional
sessionConfig- optional
model - optional
thinking - optional
workingDir - optional
skills
- optional
enabledreuseSession(defaults totrue)maxConcurrent
Notes:
- Each schedule must define
prompt,preCheck, or both. sessionConfigvalues are validated against the selected agent on create/update and revalidated when a scheduled run starts.sessionTitlestays a top-level schedule field; it is not part of scheduledsessionConfig.preCheckruns in the agentchat.config.workdirand uses the wrapper environment when configured.- When
reuseSessionistrue, scheduled runs reuse one backing session peragentId + scheduleId. - Reused scheduled sessions are created up front and reconciled from the schedule on later runs after edits.
- When
reuseSessionisfalse, each run creates a fresh backing session. maxConcurrentonly matters whenreuseSessionisfalse.- Enable the
scheduled-sessionsplugin to create, edit, delete, run, and toggle schedules, and to list/create/update/cancel pending session wake-ups and global reminders.
Wake-up tools:
scheduled_sessions_wakeup_createmessage- exactly one of
runAtordelaySeconds runAtmust be an absolute ISO timestamp with an explicit timezone offset orZ, for example2026-06-03T08:56:00-05:00or2026-06-03T13:56:00Z
scheduled_sessions_wakeup_updatewakeupId- at least one of
message,runAt, ordelaySeconds - if a time is provided, provide exactly one of
runAtordelaySeconds
scheduled_sessions_wakeup_cancelwakeupId
scheduled_sessions_wakeup_list
Wake-up tools intentionally do not accept a sessionId; they use the current tool context.
update and cancel only operate on wake-ups belonging to the current session. In a session
context, list returns manageable current-session wake-ups with IDs and message details plus
read-only other-session wake-ups with only safe summary fields: kind, scope, manageable,
runAt, status, summary, false capabilities, and an omitted list for redacted fields. The
scheduled-sessions panel uses the same plugin operation without a session context to show all
wake-ups for administration.
On Android, one-shot wake replies follow the existing Thread audio mode and Auto-listen settings: Response mode speaks then listens when Auto-listen is on, and Manual mode listens without automatic speech. Disabling Auto-listen prevents automatic recognition. No voice tool or separate wake setting is required. Each eligible connected Android device applies its own settings and notification-session filter; wakes are not tied to the device that scheduled them. This requires the updated Android app and backend. Recurring cron sessions do not receive this wake-specific recognition behavior.
Reminder tools:
scheduled_sessions_reminder_createtext: content to display and speak when due, without “remind me” or scheduling language- exactly one of
runAtordelaySeconds runAtmust be an absolute ISO timestamp with an explicit timezone offset orZ
scheduled_sessions_reminder_updatereminderId- at least one of
text,runAt, ordelaySeconds runAtanddelaySecondsare mutually exclusive when changing the delivery time
scheduled_sessions_reminder_cancelreminderId
scheduled_sessions_reminder_list
Reminder operations are global and do not require or accept a session id. The Scheduled Sessions panel displays wake-ups and reminders together in a time-ordered One-shots section. After a reminder fires, its delivered record appears in the Notifications panel and uses the existing standalone notification playback settings.
{
"chat": {
"provider": "pi",
"models": ["anthropic/claude-sonnet-4-5", "openai-codex/gpt-5.2-codex"],
"thinking": ["off", "low", "medium", "high", "xhigh"],
"config": {
"provider": "anthropic",
"apiKey": "${ANTHROPIC_API_KEY}",
"baseUrl": "https://api.anthropic.com",
"maxTokens": 4096,
"temperature": 0.7,
"maxToolIterations": 100,
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
},
"headers": {
"X-Request-Source": "assistant"
}
}
}
}models: optional list of allowed model ids (first is default). Entries may beprovider/model.thinking: optional list of allowed thinking levels (first is default). Selected level is passed to the Pi SDK reasoning option; useoffto disable reasoning.config.provider: default provider used when a model omits a prefix (required if any model omits a prefix).config.apiKey,config.baseUrl,config.headers: optional connection overrides applied when the resolved provider matchesconfig.provider.- When
config.baseUrlis set and the chosen provider has no built-in Pi model catalog entry, the server synthesizes anopenai-responsesmodel for the configuredprovider/modelid. This allows custom endpoints such as local mocks or proxies to be targeted without adding extra config fields. config.maxTokens,config.temperature,config.timeoutMs: optional Pi SDK request overrides.config.contextWindow: optional context window override used for synthesized models (providers without a built-in Pi catalog entry whenconfig.baseUrlis set).config.maxToolIterations: max consecutive tool iterations before aborting with an error (default: 100).config.compaction: optional Pi SDK context compaction controls. Compaction is enabled by default with Pi-compatible defaults:reserveTokens: 16384andkeepRecentTokens: 20000. Manual compaction is available from the chat request history menu for in-process Pi SDK sessions, and automatic threshold compaction runs after completed Pi turns when usage exceedscontextWindow - reserveTokens.
Pi SDK sessions are mirrored to the Pi JSONL format so they can be resumed by the
pi-mono CLI. Sessions are written to:
~/.pi/agent/sessions/<encoded-cwd>/*_<pi-session-id>.jsonl.
The cwd comes from attributes.core.workingDir when available (otherwise the
server working directory).
Canceled runs still write partial assistant/tool entries so the pi-mono CLI can resume.
Pi-backed sessions always persist canonical Pi JSONL history for replay and reload. Compacted Pi
JSONL logs keep the full raw history on disk and add Pi-compatible compaction entries; future
model context is rebuilt as summary plus the recent kept messages.
All CLI providers share the same config shape:
{
"chat": {
"provider": "pi-cli",
"models": ["anthropic/claude-sonnet-4-5", "openai-codex/gpt-5.2-codex"],
"thinking": ["off", "low", "medium", "high", "xhigh"],
"config": {
"wrapper": {
"path": "/home/kevin/devtools/container/run.sh",
"env": {
"PERSISTENT": "1",
"PROXY": "1",
"CONTAINER_NAME": "assistant"
}
}
}
}
}models: optional list of allowed model ids for CLI providers (first is default). Forpi-cli, entries may beprovider/modeland are split into--provider+--model. When set, do not include--modelinextraArgs(and forpi-cli, do not include--provider).thinking: optional list of allowed thinking levels forpi-cliandcodex-cli(first is default). Forcodex-cli, the selected level maps to--config model_reasoning_effort=<level>. When set, do not include--thinkinginextraArgs(pi) ormodel_reasoning_effortoverrides inextraArgs(codex).workdir: optional working directory for the CLI processextraArgs: optional extra CLI flags (reserved flags are managed by the server)wrapper.path: optional wrapper executable used to run the CLI (for containerized runs)wrapper.env: optional environment map for the wrapper (supports${ENV}substitution)
For pi-cli, history is read from the default Pi sessions directory:
~/.pi/agent/sessions/<encoded-cwd>/*_<pi-session-id>.jsonl. The cwd comes from the Pi
session header, so set workdir if you need a stable path across runs. No extra config
is required.
For claude-cli, history is read from the default Claude projects directory:
~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. The CLI defaults to the user home
directory when workdir is not set, so history typically lands under
~/.claude/projects/-home-<user>. Set workdir if you need Claude history under a
different path. No extra config is required.
For codex-cli, history is read from the default Codex sessions directory:
~/.codex/sessions/<yyyy>/<mm>/<dd>/...-<codex-session-id>.jsonl. The session id is
emitted by the CLI and tracked automatically; no extra config is required.
Reserved flags (must not be in extraArgs):
claude-cli:--output-format,--session-id,--resume,-p,--include-partial-messages,--verbosecodex-cli:--json,resumepi-cli:--mode,--session,--session-dir,--continue,-p
When chat.models is set for a CLI provider, --model is managed by the server and must not be included in extraArgs. For pi-cli, --provider is also managed by the server when chat.models is set, and --thinking is managed by the server when chat.thinking is set. For codex-cli, model_reasoning_effort is managed by the server when chat.thinking is set.
- Store API keys in environment variables or a secure secrets manager; avoid committing them to
config.json. - Limit
wrapper.pathusage to trusted scripts or containers because the wrapper runs as the server user. - MCP server commands run as the server user and inherit
envsettings, so review each command and token scope.
See data/config.example.json for a complete working example including plugins and agents.
{ "skills": [ { "root": "~/skills", "available": ["*"], "inline": ["my-critical-*"], }, ], }