Skip to content

Instantly share code, notes, and snippets.

@fank
Created July 14, 2026 19:37
Show Gist options
  • Select an option

  • Save fank/5f7d85df63956c52cf9fa08f8f140576 to your computer and use it in GitHub Desktop.

Select an option

Save fank/5f7d85df63956c52cf9fa08f8f140576 to your computer and use it in GitHub Desktop.
Repro for vllm-project/vllm#48645 — deepseek_v4 parser routes answer to reasoning_content when the model omits </think>
#!/usr/bin/env python3
"""
Repro for vllm-project/vllm#48645 — deepseek_v4 parser: a reply that omits `</think>`
routes the entire answer to reasoning_content, leaving content empty (trailing EOS un-stripped).
Server must run (as the DeepSeek-V4-Flash recipe suggests):
--reasoning-parser deepseek_v4 --tool-call-parser deepseek_v4 --enable-auto-tool-choice
--default-chat-template-kwargs={"thinking":true,"reasoning_effort":"high"}
The request deliberately sends NO chat_template_kwargs — like any ordinary OpenAI-compatible
agent client — so the server default thinking=true applies and the parser starts in REASONING.
Usage:
export VLLM_BASE_URL=http://localhost:8000/v1
export VLLM_API_KEY=dummy
python3 repro_48645.py [N]
repro_payload.json holds the system prompt + tool definitions from a real coding-agent client
(`pi`), scrubbed of local paths. A generic or synthetic prompt does NOT trigger this — the
instruction content is what makes the model answer without deliberating (see issue for rates).
"""
import json, os, sys, urllib.request
from concurrent.futures import ThreadPoolExecutor
BASE = os.environ.get("VLLM_BASE_URL", "http://localhost:8000/v1").rstrip("/")
KEY = os.environ.get("VLLM_API_KEY", "dummy")
MODEL = os.environ.get("VLLM_MODEL", "DeepSeek-V4-Flash")
EOS = "end▁of▁sentence" # <|end▁of▁sentence|> (|=U+FF5C, ▁=U+2581)
P = json.load(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "repro_payload.json")))
def once(_):
body = {"model": MODEL, "stream": True, "tools": P["tools"],
"messages": [{"role": "system", "content": P["system"]},
{"role": "user", "content": "good morning"}]}
req = urllib.request.Request(BASE + "/chat/completions", json.dumps(body).encode(),
{"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
content = reasoning = ""
for line in urllib.request.urlopen(req, timeout=300):
line = line.decode().strip()
if not line.startswith("data: ") or line == "data: [DONE]":
continue
choices = json.loads(line[6:])["choices"]
if not choices:
continue
d = choices[0].get("delta") or {}
content += d.get("content") or ""
reasoning += d.get("reasoning_content") or d.get("reasoning") or ""
return {"misrouted": (not content.strip()) and EOS in reasoning,
"content": content, "reasoning": reasoning}
N = int(sys.argv[1]) if len(sys.argv) > 1 else 20
with ThreadPoolExecutor(8) as ex:
res = list(ex.map(once, range(N)))
bad = [r for r in res if r["misrouted"]]
print(f"misrouted (content empty + answer&EOS in reasoning): {len(bad)}/{N}")
for r in bad[:3]:
print(f" reasoning={r['reasoning'][-70:]!r}\n content={r['content']!r}")
if not bad:
print(" no misroute in this run — it is sampling-dependent (~20-25%); try a larger N")
{"system": "You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n- read: Read file contents\n- bash: Execute bash commands (ls, grep, find, etc.)\n- edit: Make precise file edits with exact text replacement, including multiple disjoint edits in one call\n- write: Create or overwrite files\n- mcp: MCP gateway - connect to MCP servers and call their tools\n- workflow: Run a deterministic JavaScript workflow. Required script header: export const meta = { name: 'short_snake_case', description: 'non-empty description', phases: [{ title: 'Phase' }] }.\n- hypa_shell: Run shell commands through Hypa compression\n- hypa_read: Read file contents through Hypa compression\n- hypa_grep: Search file contents through Hypa compression\n- hypa_find: Find files through Hypa compression\n- hypa_ls: List directory contents through Hypa compression\n- ask_user_question: Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous\n- todo: Manage a task list to track multi-step progress\n- goal_complete: Mark the active /goal as complete after fully finishing and verifying it, with the current goal_id\n- goal_blocked: Mark the active /goal blocked only after the same blocker recurs for three consecutive goal turns\n- process_thought: Record one thought at a time in a structured thinking session.\n- generate_summary: Summarize the thoughts recorded in a session.\n- clear_history: Reset a session by clearing all recorded thoughts.\n- export_session: Export a session's thoughts to a JSON file.\n- import_session: Restore thoughts from a previously exported JSON file.\n- get_thinking_history: Read recorded thoughts with pagination and optional snippet mode.\n- get_thinking_status: Read content-free storage and configuration diagnostics.\n- sequential_think: Scaffold a complete staged thinking sequence in one call.\n- web_search: Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage.\n- fetch_content: Use to extract readable content from URL(s), YouTube, GitHub repos, or local videos. For video questions, pass the user's exact question in prompt.\n- get_search_content: Use after web_search/fetch_content when full stored content is needed via responseId plus query/url selectors.\n- monitor: monitor — run a background shell command whose stdout lines become events you react to\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n- Use bash for file operations like ls, rg, find\n- Use read to examine files instead of cat or sed.\n- Use edit for precise changes (edits[].oldText must match exactly)\n- When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls\n- Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.\n- Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.\n- Use write only for new files or complete rewrites.\n- Use workflow only when the user explicitly asks for a workflow, workflows, fan-out, or multi-agent orchestration.\n- For workflow, always pass one raw JavaScript string in the required script parameter; do not include Markdown fences or prose around the script.\n- For workflow, the script's first statement must be `export const meta = { name: 'short_snake_case', description: 'non-empty human description', phases: [{ title: 'Phase name' }] }`; meta.name and meta.description are required non-empty strings.\n- For workflow, write plain JavaScript after the meta export. Do not use TypeScript syntax, imports, require(), fs, Date.now(), Math.random(), or new Date().\n- For workflow, available globals are agent(prompt, opts), parallel(thunks), pipeline(items, ...stages), phase(title), log(message), args, cwd, process.cwd(), and budget. Every workflow must call agent() at least once; do not use workflow only to declare phases or return a static object.\n- For workflow, prefer the built-in quality helpers when they fit (each is built on agent()/parallel() and returns plain data): verify(item, {reviewers, threshold, lens}) for adversarial fact-checking; judgePanel(attempts, {judges, rubric}) to score N candidates and return the best; loopUntilDry({round, key, consecutiveEmpty}) to keep finding until rounds stop yielding new items; completenessCheck(args, results) as a final 'what's missing' critic.\n- For workflow, when meta.phases declares more than one phase, call phase('Exact Title') at the start of each phase's work (or set opts.phase on each agent) so every agent groups under the correct phase; never declare a phase you don't switch into — a declared phase with no agents shows as 0/0 and any agent you forgot to move stays in the previous phase.\n- For workflow, do not set tokenBudget or agentTimeoutMs unless the user explicitly asks to cap spend or time; the defaults are unbounded.\n- For workflow, to bound spend: pass tokenBudget for a hard run-wide cap; carve a per-phase ceiling with phase('Name', {budget: N}) (that phase throws at its sub-budget without touching the run total — wrap its work in try/catch so later phases proceed); use retry(thunk, {attempts, until}) for bounded retry, and gate(thunk, validator, {attempts}) when a validator's feedback should steer the next attempt. To degrade gracefully, branch on budget.remaining() to skip optional rounds or choose a lighter tier.\n- For workflow, prefer it for decomposable work: repository inspection, independent research/checks, multi-perspective review, or fan-out/fan-in synthesis. Do not use it for a single quick file read/edit or when ordinary tools are enough.\n- For workflow, parallel() takes functions, not promises: use `await parallel(items.map(item => () => agent('...', { label: '...' })))`, never `await parallel(items.map(item => agent(...)))`. Results are returned in input order.\n- For workflow, pipeline(items, ...stages) runs each item through stages sequentially, while different items may run concurrently. Each stage receives (previousValue, originalItem, index).\n- For workflow, every agent() call should include a unique short label option, 2-5 words, such as { label: 'repo inventory' } or { label: 'source modules' }; unique labels make live status and error reporting readable.\n- For workflow, use low concurrency and agentRetries for unstable provider/transport fan-out runs; retries apply only to recoverable agent failures and still require explicit null handling after exhaustion.\n- For workflow, failed agent(), parallel(), or pipeline() branches return null and log the failure unless the workflow is aborted. Check for nulls before synthesizing conclusions.\n- For workflow, include a final synthesis/assertion agent when combining multiple subagent results; return a compact JSON-serializable value with ok/verdict plus the important outputs.\n- For workflow, the default quality shape for fan-out work is finder -> verify -> merge: run one agent per angle or work-unit (in parallel), pass each candidate finding through verify() and drop the unconfirmed, then a single synthesis agent that de-duplicates, ranks by confidence/severity, and caps the output. If nothing survives verification, return an empty result and say so rather than padding.\n- For workflow, give each subagent a substantive, self-contained task: do not spawn an agent just to read one file or run one command, and do not use one agent only to check on another. Prefer fewer, higher-level agents over many trivial micro-tasks.\n- For workflow, if agent() needs machine-readable output, pass a plain JSON Schema via opts.schema; agent() will return the validated object. Use JSON Schema syntax, not TypeScript or TypeBox constructors.\n- For workflow, the user configures per-tier models (/workflows-models), so TAG EVERY agent with opts.tier by role so those models are actually used. opts.tier accepts 'small', 'medium', or 'big' and is enforced at runtime. Small tier: lightweight exploration/search/inventory agents. Medium tier: balanced analysis agents. Big tier: synthesis/judgment/decision agents spanning the full context. An agent with no opts.tier and no opts.model falls back to the user's medium tier; do not rely on that — tag agents explicitly so small/big are used where they fit. If the user named a specific model, use opts.model with that exact provider/id; opts.model always takes precedence over opts.tier. Exact model specs may include Pi CLI-style thinking suffixes such as openai-codex/gpt-5.5:xhigh or anthropic/claude-fable-5:max when the user requests a specific effort level. The user's currently available models (route only to these) are: anthropic/claude-fable-5, anthropic/claude-haiku-4-5, anthropic/claude-haiku-4-5-20251001, anthropic/claude-opus-4-1, anthropic/claude-opus-4-1-20250805, anthropic/claude-opus-4-5, anthropic/claude-opus-4-5-20251101, anthropic/claude-opus-4-6, anthropic/claude-opus-4-7, anthropic/claude-opus-4-8, anthropic/claude-sonnet-4-5, anthropic/claude-sonnet-4-5-20250929, anthropic/claude-sonnet-4-6, anthropic/claude-sonnet-5, google/gemini-2.0-flash, google/gemini-2.0-flash-lite, google/gemini-2.5-flash, google/gemini-2.5-flash-lite, google/gemini-2.5-pro, google/gemini-3-flash-preview, google/gemini-3-pro-preview, google/gemini-3.1-flash-lite, google/gemini-3.1-flash-lite-preview, google/gemini-3.1-pro-preview, google/gemini-3.1-pro-preview-customtools, google/gemini-3.5-flash, google/gemini-flash-latest, google/gemini-flash-lite-latest, google/gemma-4-26b-a4b-it, google/gemma-4-31b-it, openai/gpt-4, openai/gpt-4-turbo, openai/gpt-4.1, openai/gpt-4.1-mini, openai/gpt-4.1-nano, openai/gpt-4o, openai/gpt-4o-2024-05-13, openai/gpt-4o-2024-08-06, openai/gpt-4o-2024-11-20, openai/gpt-4o-mini, openai/gpt-5, openai/gpt-5-chat-latest, openai/gpt-5-codex, openai/gpt-5-mini, openai/gpt-5-nano, openai/gpt-5-pro, openai/gpt-5.1, openai/gpt-5.1-chat-latest, openai/gpt-5.1-codex, openai/gpt-5.1-codex-max, openai/gpt-5.1-codex-mini, openai/gpt-5.2, openai/gpt-5.2-chat-latest, openai/gpt-5.2-codex, openai/gpt-5.2-pro, openai/gpt-5.3-chat-latest, openai/gpt-5.3-codex, openai/gpt-5.3-codex-spark, openai/gpt-5.4, openai/gpt-5.4-mini, openai/gpt-5.4-nano, openai/gpt-5.4-pro, openai/gpt-5.5, openai/gpt-5.5-pro, openai/gpt-5.6-luna, openai/gpt-5.6-sol, openai/gpt-5.6-terra, openai/o1, openai/o1-pro, openai/o3, openai/o3-deep-research, openai/o3-mini, openai/o3-pro, openai/o4-mini, openai/o4-mini-deep-research, openai/DeepSeek-V4-Flash, spark1/nemotron, capture/DeepSeek-V4-Flash.\n- For workflow, do not assume the parent assistant has repository code context inside subagents; include enough task context and relevant paths in each agent prompt.\n- For workflow, runs are background by default: the tool returns immediately with a run ID, the turn ends so the user isn't blocked, and the result is delivered back into the conversation when the run finishes. Pass background: false only when you must use the result inline in this same turn (it will block).\n- For workflow, you may call `await workflow('saved-name', argsObject)` to run a saved workflow inline and use its result; nesting is one level deep only, and the global 16-concurrent / 1000-total caps hold across the nesting.\n- Use hypa_shell for shell commands when compressed output is preferred.\n- Do not use hypa_shell to read files; use hypa_read instead.\n- Use hypa_read to inspect file contents instead of cat/head/tail via shell.\n- Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.\n- Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer (\"Type something.\" row is appended automatically to single-select questions) or pick \"Chat about this\" to abandon the questionnaire.\n- Set multiSelect: true when multiple answers are valid; this suppresses the \"Type something.\" row. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. NOTE: any non-empty preview on a single-select question ALSO suppresses the \"Type something.\" row (no room in the side-by-side layout); \"Chat about this\" remains the escape hatch. If you recommend a specific option, make it the first option and append \"(Recommended)\" to its label.\n- Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.\n- Use `todo` for complex work with 3+ steps, when the user gives you a list of tasks, or immediately after receiving new instructions to capture requirements. Skip it for single trivial tasks and purely conversational requests.\n- When starting any task, mark it in_progress BEFORE beginning work. Mark it completed IMMEDIATELY when done — never batch completions. Exactly one task should be in_progress at a time.\n- Never mark a task completed if tests are failing, the implementation is partial, or you hit unresolved errors — keep it in_progress and create a new task for the blocker instead.\n- Task status is a 4-state machine: pending → in_progress → completed, plus deleted as a tombstone. Pass activeForm (present-continuous label, e.g. 'researching existing tool') when marking in_progress.\n- Use blockedBy to express dependencies (A is blocked by B). On create, pass blockedBy as the initial set. On update, use addBlockedBy / removeBlockedBy (additive merge — do not resend the full array). Cycles are rejected.\n- list hides tombstoned (deleted) tasks by default; pass includeDeleted:true to see them. Pass status to filter by a single status.\n- Subject must be short and imperative (e.g. 'Research existing tool'); description is for long-form detail. activeForm is a present-continuous label shown while in_progress.\n- When a /goal is active, keep working until the goal is complete; do not stop with only a plan or partial progress.\n- Before calling goal_complete, audit the active goal requirement by requirement against the current files, command output, tests, or external state.\n- Pass the exact goal_id shown in the current /goal prompt; never reuse a goal_id from an older, stopped, replaced, or cleared turn.\n- Call goal_complete only after the requested goal is fully implemented, verified, and no known required work remains; otherwise keep working.\n- Use goal_blocked only for a true impasse after the same blocker recurs for at least three consecutive goal turns and concrete evidence shows user or external action is required.\n- After a blocked goal is resumed, start a fresh three-turn blocker audit before using goal_blocked again.\n- Do not use goal_blocked for ordinary clarification, incomplete work, uncertainty, difficult tasks, or recoverable tool/provider failures.\n- Pass goal_blocked the exact current goal_id; never reuse a goal_id from an older, stopped, replaced, or cleared goal turn.\n- Use process_thought to record one thought at a time; use sequential_think to scaffold a full 3-10 stage sequence in one call.\n- Use process_thought when you control each thought's content; use sequential_think when you want pre-filled stage prompts.\n- Use process_thought to extend an existing session; use clear_history to start fresh.\n- Use generate_summary for an aggregate view (stages, tags, completion); use get_thinking_history when you need the full thought text.\n- Use generate_summary for content-derived overview; use get_thinking_status for content-free storage and configuration diagnostics.\n- Use clear_history to start a session fresh; use export_session first if you want to preserve the current thoughts.\n- Use clear_history to reset a specific named session via session_id; omit session_id to reset the default session.\n- Use export_session to write a session's thoughts to a file; use import_session to restore them later.\n- Use export_session before clear_history to preserve the current thoughts; use generate_summary when you only need an overview.\n- Use import_session to load thoughts from a JSON file; use export_session to create such a file.\n- Use import_session to overwrite the target session's thoughts; specify session_id to pick a non-default target.\n- Use get_thinking_history to read full thought content; use get_thinking_status when you only need storage and configuration diagnostics.\n- Use get_thinking_history with include_full_thoughts=false for compact 120-char snippets; default true returns the complete text.\n- Use get_thinking_history for raw thoughts; use generate_summary for an aggregate stages/tags/completion view.\n- Use get_thinking_status for storage health and config diagnostics; use get_thinking_history when you need the actual thought content.\n- Use get_thinking_status to detect corrupt sessions, non-writable storage, or config source labels; not for reading thought text.\n- Use sequential_think to generate 3-10 stage prompts at once; use process_thought when you want to record your own thoughts step-by-step.\n- Use sequential_think when starting fresh on a topic; use process_thought to extend an existing session with your own content.\n- Use monitor for anything long-running that emits progress (log tails, CI polling, dev servers) instead of `bash` with `sleep` loops.\n- When piping in monitor, always add `grep --line-buffered` (or `stdbuf -oL`). Without it, pipe buffering silently delays events by minutes.\n- In poll loops inside monitor, append `|| true` after network calls so one transient failure does not kill the monitor.\n- Be selective with stdout in monitor. Monitors emitting more than 50 lines/sec over 10s are auto-stopped — filter before piping into the stream.\n- monitor forwards stdout only; stderr is captured to a file. Use monitor_read_stderr when you need to debug a silent crash or non-zero exit.\n- Call monitor_list before spawning duplicates. Call monitor_stop when the monitor is no longer useful.\n- Be concise in your responses\n- Show file paths clearly when working with files\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: /home/user/.nvm/versions/node/v24.1.0/lib/node_modules/@earendil-works/pi-coding-agent/README.md\n- Additional docs: /home/user/.nvm/versions/node/v24.1.0/lib/node_modules/@earendil-works/pi-coding-agent/docs\n- Examples: /home/user/.nvm/versions/node/v24.1.0/lib/node_modules/@earendil-works/pi-coding-agent/examples (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>find-skills</name>\n <description>Helps users discover and install agent skills when they ask questions like &quot;how do I do X&quot;, &quot;find a skill for X&quot;, &quot;is there a skill that can...&quot;, or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.</description>\n <location>/home/user/.agents/skills/find-skills/SKILL.md</location>\n </skill>\n <skill>\n <name>security-review</name>\n <description>Security code review for vulnerabilities. Use when asked to &quot;security review&quot;, &quot;find vulnerabilities&quot;, &quot;check for security issues&quot;, &quot;audit security&quot;, &quot;OWASP review&quot;, or review code for injection, XSS, authentication, authorization, cryptography issues. Provides systematic review with confidence-based reporting.</description>\n <location>/home/user/.agents/skills/security-review/SKILL.md</location>\n </skill>\n <skill>\n <name>pi-deep-research</name>\n <description>Deep web research with adaptive planning, multi-hop reasoning, and confidence-driven iteration. For research questions beyond knowledge cutoff, competitive analysis, technology surveys, literature reviews. Use when the user asks to research, investigate, or find up-to-date information on any topic.\n</description>\n <location>/home/user/.pi/agent/git/github.com/user/pi-deep-research/pi-deep-research/SKILL.md</location>\n </skill>\n <skill>\n <name>pi-subagents</name>\n <description>Delegate work to builtin or custom subagents with single-agent, chain,\nparallel, async, forked-context, and intercom-coordinated workflows. Use\nfor advisory review, implementation handoffs, and multi-step tasks where a\nsingle agent should stay in control while other agents contribute context,\nplanning, or execution.\n</description>\n <location>/home/user/.pi/agent/npm/node_modules/pi-subagents/skills/pi-subagents/SKILL.md</location>\n </skill>\n <skill>\n <name>librarian</name>\n <description>Research open-source libraries with evidence-backed answers and GitHub permalinks. Use when the user asks about library internals, needs implementation details with source code references, wants to understand why something was changed, or needs authoritative answers backed by actual code. Excels at navigating large open-source repos and providing citations to exact lines of code.</description>\n <location>/home/user/.pi/agent/git/github.com/user/pi-web-access/skills/librarian/SKILL.md</location>\n </skill>\n <skill>\n <name>rule-authoring</name>\n <description>Author path-scoped rule files for pi-rules. Use when: creating/editing .pi/rules/*.md or .claude/rules/*.md | &quot;add a project rule&quot; | &quot;write a pi rule&quot; | designing always-on conventions. Triggers: &quot;rule&quot;, &quot;convention&quot;, &quot;paths&quot;.</description>\n <location>/home/user/.pi/agent/npm/node_modules/@the-forge-flow/pi-rules/dist/skills/rule-authoring/SKILL.md</location>\n </skill>\n</available_skills>\nCurrent date: 2026-07-14\nCurrent working directory: /home/user/project", "tools": [{"type": "function", "function": {"name": "read", "description": "Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to 2000 lines or 50KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.", "parameters": {"type": "object", "required": ["path"], "properties": {"path": {"type": "string", "description": "Path to the file to read (relative or absolute)"}, "offset": {"type": "number", "description": "Line number to start reading from (1-indexed)"}, "limit": {"type": "number", "description": "Maximum number of lines to read"}}}, "strict": false}}, {"type": "function", "function": {"name": "bash", "description": "Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last 2000 lines or 50KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.", "parameters": {"type": "object", "required": ["command"], "properties": {"command": {"type": "string", "description": "Bash command to execute"}, "timeout": {"type": "number", "description": "Timeout in seconds (optional, no default timeout)"}}}, "strict": false}}, {"type": "function", "function": {"name": "edit", "description": "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.", "parameters": {"type": "object", "required": ["path", "edits"], "properties": {"path": {"type": "string", "description": "Path to the file to edit (relative or absolute)"}, "edits": {"type": "array", "items": {"type": "object", "required": ["oldText", "newText"], "properties": {"oldText": {"type": "string", "description": "Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call."}, "newText": {"type": "string", "description": "Replacement text for this targeted edit."}}}, "description": "One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead."}}}, "strict": false}}, {"type": "function", "function": {"name": "write", "description": "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.", "parameters": {"type": "object", "required": ["path", "content"], "properties": {"path": {"type": "string", "description": "Path to the file to write (relative or absolute)"}, "content": {"type": "string", "description": "Content to write to the file"}}}, "strict": false}}, {"type": "function", "function": {"name": "mcp", "description": "MCP gateway - connect to MCP servers and call their tools. Non-MCP Pi tools should be called directly, not through mcp.\n\nUsage:\n mcp({ }) → Show server status\n mcp({ server: \"name\" }) → List tools from server\n mcp({ search: \"query\" }) → Search MCP tools by name/description\n mcp({ describe: \"tool_name\" }) → Show tool details and parameters\n mcp({ connect: \"server-name\" }) → Connect to a server and refresh metadata\n mcp({ tool: \"name\", args: '{\"key\": \"value\"}' }) → Call a tool (args is JSON string)\n mcp({ action: \"ui-messages\" }) → Retrieve accumulated messages from completed UI sessions\n mcp({ action: \"auth-start\", server: \"name\" }) → Start manual OAuth and get a browser URL\n mcp({ action: \"auth-complete\", server: \"name\", args: '{\"redirectUrl\":\"...\"}' }) → Complete manual OAuth\n\nMode: action > tool (call) > connect > describe > search > server (list) > nothing (status)", "parameters": {"type": "object", "properties": {"tool": {"type": "string", "description": "Tool name to call (e.g., 'xcodebuild_list_sims')"}, "args": {"type": "string", "description": "Arguments as JSON string (e.g., '{\"key\": \"value\"}')"}, "connect": {"type": "string", "description": "Server name to connect (lazy connect + metadata refresh)"}, "describe": {"type": "string", "description": "Tool name to describe (shows parameters)"}, "search": {"type": "string", "description": "Search tools by name/description"}, "regex": {"type": "boolean", "description": "Treat search as regex (default: substring match)"}, "includeSchemas": {"type": "boolean", "description": "Include parameter schemas in search results (default: true)"}, "server": {"type": "string", "description": "Filter to specific server (also disambiguates tool calls)"}, "action": {"type": "string", "description": "Action: 'ui-messages', 'auth-start', or 'auth-complete'"}}}, "strict": false}}, {"type": "function", "function": {"name": "workflow", "description": "Execute a deterministic JavaScript workflow that orchestrates multiple subagents with agent(), parallel(), and pipeline(). script is required raw JavaScript. It must start with export const meta = { name, description, phases? } and must call agent() at least once.", "parameters": {"type": "object", "required": ["script"], "properties": {"script": {"type": "string", "description": "Required raw JavaScript workflow script, with no Markdown fences. First statement: export const meta = { name: 'short_snake_case', description: 'non-empty description', phases: [{ title: 'Phase' }] } Use phase('Name'), agent(prompt, opts), parallel(arrayOfFunctions), pipeline(items, ...stages), log(message), args, and budget. The workflow must call agent() at least once. parallel() requires functions, not promises: await parallel(items.map(item => () => agent(...)))."}, "args": {"description": "Optional JSON value exposed to the workflow script as global `args`."}, "background": {"type": "boolean", "description": "Run the workflow in the background. Default: true — the tool returns immediately with a run ID, the turn ends so the user isn't blocked, and the result is delivered back into the conversation when it finishes. Set to false only when you need the result inline in this same turn (the call will block until the workflow completes)."}, "maxAgents": {"type": "number", "description": "Maximum number of agents allowed in this run. Default: 1000."}, "concurrency": {"type": "number", "description": "Maximum concurrent agents for this run. Clamped to the runtime maximum. Use when provider/transport stability matters."}, "agentRetries": {"type": "number", "description": "Retry attempts for recoverable agent failures such as timeout, connection failure, or empty assistant output. Default 0 unless configured."}, "agentTimeoutMs": {"type": "number", "description": "Timeout per agent in milliseconds. Omit for no hard timeout by default. Set only when the user asks to bound time."}, "tokenBudget": {"type": "number", "description": "Hard total-token budget for the whole run. Once spent reaches it, further agent() calls fail and the run stops. Omit for no limit. Set it when the user asks to cap spend."}}}, "strict": false}}, {"type": "function", "function": {"name": "hypa_shell", "description": "Run shell commands through Hypa compression. Output is truncated to 2000 lines or 50KB with full output saved when needed.", "parameters": {"type": "object", "properties": {"command": {"type": "string", "description": "Shell command to execute through Hypa compression"}, "timeoutMs": {"type": "number", "description": "Timeout in milliseconds (default: Hypa CLI default)"}, "raw": {"type": "boolean", "description": "Run with hypa raw instead of compressed hypa -c"}}, "required": ["command"], "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "hypa_read", "description": "Read a file through Hypa compression. Supports offset/limit line slices. Output is truncated to 2000 lines or 50KB with full output saved when needed.", "parameters": {"type": "object", "properties": {"path": {"type": "string", "description": "Path to read, relative to Pi cwd or absolute"}, "offset": {"type": "number", "description": "Line number to start reading from (1-indexed)"}, "limit": {"type": "number", "description": "Maximum number of lines to read"}, "maxTokens": {"type": "number", "description": "Approximate maximum tokens to return after Hypa compression"}}, "required": ["path"], "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "hypa_grep", "description": "Search file contents with ripgrep through Hypa compression. Output is truncated to 2000 lines or 50KB with full output saved when needed.", "parameters": {"type": "object", "properties": {"pattern": {"type": "string", "description": "Search pattern"}, "path": {"type": "string", "description": "Directory or file to search (default: current directory)"}, "glob": {"type": "string", "description": "File glob filter, e.g. *.ts"}, "ignoreCase": {"type": "boolean", "description": "Case-insensitive search"}, "literal": {"type": "boolean", "description": "Treat pattern as a literal string"}, "context": {"type": "number", "description": "Lines of context around each match"}, "limit": {"type": "number", "description": "Maximum matches"}, "timeoutMs": {"type": "number", "description": "Timeout in milliseconds (default: Hypa CLI default)"}}, "required": ["pattern"], "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "hypa_find", "description": "Find files through Hypa compression. Output is truncated to 2000 lines or 50KB with full output saved when needed.", "parameters": {"type": "object", "properties": {"pattern": {"type": "string", "description": "File name/glob pattern (default: *)"}, "path": {"type": "string", "description": "Directory to search (default: current directory)"}, "limit": {"type": "number", "description": "Maximum paths to return"}, "timeoutMs": {"type": "number", "description": "Timeout in milliseconds (default: Hypa CLI default)"}}, "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "hypa_ls", "description": "List directory contents through Hypa compression. Output is truncated to 2000 lines or 50KB with full output saved when needed.", "parameters": {"type": "object", "properties": {"path": {"type": "string", "description": "Directory to list (default: current directory)"}, "all": {"type": "boolean", "description": "Include dotfiles"}, "long": {"type": "boolean", "description": "Use long listing"}, "timeoutMs": {"type": "number", "description": "Timeout in milliseconds (default: Hypa CLI default)"}}, "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "ask_user_question", "description": "Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users will always be able to type a custom answer (\"Type something.\" row is appended automatically to every single-select question) or pick \"Chat about this\" to abandon the questionnaire and continue in free-form conversation. Do NOT author \"Other\" / \"Type something.\" / \"Chat about this\" labels yourself — duplicates are rejected at runtime.\n- Use multiSelect: true to allow multiple answers to be selected for a question. The \"Type something.\" row is suppressed on multi-select questions, and is ALSO suppressed on single-select questions where any option carries a `preview` (the side-by-side layout has no room for inline custom text — \"Chat about this\" remains as the free-form escape hatch).\n- If you recommend a specific option, make that the first option in the list and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional `preview` field on options when presenting concrete artifacts that users need to visually compare:\n- ASCII mockups of UI layouts or components\n- Code snippets showing different implementations\n- Diagram variations\n- Configuration examples\n\nPreview content is rendered as markdown in a monospace box. Multi-line text with newlines is supported. When any option has a preview, the UI switches to a side-by-side layout with a vertical option list on the left and preview on the right. Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).", "parameters": {"type": "object", "required": ["questions"], "properties": {"questions": {"type": "array", "items": {"type": "object", "required": ["question", "header", "options"], "properties": {"question": {"type": "string", "description": "The complete question to ask the user. Should be clear, specific, and end with a question mark. Example: \"Which library should we use for date formatting?\" If multiSelect is true, phrase it accordingly, e.g. \"Which features do you want to enable?\""}, "header": {"type": "string", "maxLength": 16, "description": "MAX 16 CHARACTERS — hard limit, requests over the limit are rejected. Very short chip/tag shown next to the question. Examples: \"Auth method\", \"Library\", \"Approach\"."}, "options": {"type": "array", "items": {"type": "object", "required": ["label", "description"], "properties": {"label": {"type": "string", "maxLength": 60, "description": "MAX 60 CHARACTERS — hard limit, requests over the limit are rejected. The display text for this option that the user will see and select. Should be concise (1-5 words) and clearly describe the choice."}, "description": {"type": "string", "description": "Explanation of what this option means or what will happen if chosen. Useful for providing context about trade-offs or implications."}, "preview": {"type": "string", "description": "Optional preview content rendered when this option is focused. Use for mockups, code snippets, or visual comparisons that help users compare options. See the tool description for the expected content format."}}}, "minItems": 2, "maxItems": 4, "description": "The available choices for this question. Must have 2-4 options. Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). The 'Type something.' row is appended automatically — do NOT author it."}, "multiSelect": {"type": "boolean", "default": false, "description": "Set to true to allow the user to select multiple options instead of just one. Use when choices are not mutually exclusive."}}}, "minItems": 1, "maxItems": 4, "description": "Questions to ask the user (1-4 questions)"}}}, "strict": false}}, {"type": "function", "function": {"name": "todo", "description": "Manage a task list for tracking multi-step progress. Actions: create (new task), update (change status/fields/dependencies), list (all tasks, optionally filtered by status), get (single task details), delete (tombstone), clear (reset all). Status: pending → in_progress → completed, plus deleted tombstone. Use this to plan and track multi-step work like research, design, and implementation.", "parameters": {"type": "object", "required": ["action"], "properties": {"action": {"type": "string", "enum": ["create", "update", "list", "get", "delete", "clear"]}, "subject": {"type": "string", "description": "Task subject line (required for create)"}, "description": {"type": "string", "description": "Long-form task description"}, "activeForm": {"type": "string", "description": "Present-continuous spinner label shown while status is in_progress (e.g. 'writing tests')"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed", "deleted"], "description": "Target status (update) or list filter (list)"}, "blockedBy": {"type": "array", "items": {"type": "number"}, "description": "Initial blockedBy ids (create only)"}, "addBlockedBy": {"type": "array", "items": {"type": "number"}, "description": "Task ids to add to blockedBy (update only, additive merge)"}, "removeBlockedBy": {"type": "array", "items": {"type": "number"}, "description": "Task ids to remove from blockedBy (update only, additive merge)"}, "owner": {"type": "string", "description": "Agent/owner assigned to this task"}, "metadata": {"type": "object", "patternProperties": {"^.*$": {}}, "description": "Arbitrary metadata; pass null value for a key to delete that key on update"}, "id": {"type": "number", "description": "Task id (required for update, get, delete)"}, "includeDeleted": {"type": "boolean", "description": "If true, list action returns deleted (tombstoned) tasks as well. Default: false."}}}, "strict": false}}, {"type": "function", "function": {"name": "research_checkpoint", "description": "MANDATORY after each search round during deep research. Submit current research state for evaluation. The tool will analyze your progress and return a VERDICT: CONTINUE (must search more) or PROCEED (may synthesize report). You MUST obey the verdict — if it says CONTINUE, you must do another search round before calling this again. Do NOT skip this tool or write the report without a PROCEED verdict.", "parameters": {"type": "object", "required": ["depth", "round", "sub_questions", "total_sources"], "properties": {"depth": {"type": "string", "description": "Research depth level: \"quick\", \"standard\", \"deep\", or \"exhaustive\""}, "round": {"type": "number", "description": "Current search round number (1-indexed, increment after each search batch)"}, "sub_questions": {"type": "array", "items": {"type": "object", "required": ["question", "answered", "confidence", "source_count", "best_source_tier"], "properties": {"question": {"type": "string", "description": "The sub-question"}, "answered": {"type": "boolean", "description": "Whether this sub-question has been adequately answered"}, "confidence": {"type": "number", "description": "Confidence score 0-100 for this sub-question"}, "source_count": {"type": "number", "description": "Number of sources found for this sub-question"}, "best_source_tier": {"type": "number", "description": "Best source credibility tier (1=authoritative, 2=reliable, 3=community, 4=unverified)"}}}, "description": "Status of each sub-question"}, "total_sources": {"type": "number", "description": "Total unique sources collected so far"}, "contradictions": {"type": "array", "items": {"type": "string"}, "description": "List of contradictions found between sources"}, "gaps": {"type": "array", "items": {"type": "string"}, "description": "Known information gaps that remain"}}}, "strict": false}}, {"type": "function", "function": {"name": "goal_complete", "description": "Mark the active /goal as complete after all required work is done and verified, using the current goal_id stale-turn guard. Do not use for partial progress, blockers, failing, or unverified work.", "parameters": {"type": "object", "required": ["goal_id", "summary"], "properties": {"goal_id": {"type": "string", "description": "The exact goal_id shown in the current active /goal prompt. Used only to reject stale completion calls from older turns."}, "summary": {"type": "string", "description": "State what was completed and what evidence verified it. Do not use this tool to report partial progress, blockers, failures, or remaining work."}}}, "strict": false}}, {"type": "function", "function": {"name": "goal_blocked", "description": "Stop the active /goal only at a true impasse after the same blocker recurs for at least three consecutive goal turns, with the current goal_id and concrete evidence that user or external action is required. Do not use for ordinary clarification, uncertainty, or recoverable failures.", "parameters": {"type": "object", "required": ["goal_id", "reason", "evidence", "repeated_turns"], "properties": {"goal_id": {"type": "string", "description": "The exact goal_id shown in the current active /goal prompt."}, "reason": {"type": "string", "minLength": 1, "maxLength": 1000, "description": "The specific user or external action required to unblock the goal."}, "evidence": {"type": "string", "minLength": 1, "maxLength": 4000, "description": "Concrete evidence from the repeated attempts that proves the impasse."}, "repeated_turns": {"type": "integer", "minimum": 3, "description": "Number of separate turns spent trying to resolve this same blocker."}}}, "strict": false}}, {"type": "function", "function": {"name": "process_thought", "description": "Record and analyze a sequential thought with metadata. Use this to break down complex problems into structured steps through stages: Problem Definition, Research, Analysis, Synthesis, Conclusion. Accepts snake_case fields and MCP-style camelCase aliases. Content-bearing: stores thought text in local plaintext JSON.", "parameters": {"type": "object", "required": ["thought", "stage"], "properties": {"thought": {"type": "string", "description": "The content of your thought."}, "thought_number": {"type": "integer", "minimum": 1, "description": "Position in your sequence. Required at runtime — supply this field or its camelCase alias thoughtNumber."}, "thoughtNumber": {"type": "integer", "minimum": 1, "description": "camelCase alias for thought_number. Required at runtime — supply either form."}, "total_thoughts": {"type": "integer", "minimum": 1, "description": "Expected total thoughts in the sequence. Required at runtime — supply this field or its camelCase alias totalThoughts."}, "totalThoughts": {"type": "integer", "minimum": 1, "description": "camelCase alias for total_thoughts. Required at runtime — supply either form."}, "next_thought_needed": {"type": "boolean", "description": "Whether more thoughts are needed after this one. Required at runtime — supply this field or its camelCase alias nextThoughtNeeded."}, "nextThoughtNeeded": {"type": "boolean", "description": "camelCase alias for next_thought_needed. Required at runtime — supply either form."}, "stage": {"anyOf": [{"type": "string", "const": "Problem Definition"}, {"type": "string", "const": "Research"}, {"type": "string", "const": "Analysis"}, {"type": "string", "const": "Synthesis"}, {"type": "string", "const": "Conclusion"}], "description": "The thinking stage."}, "tags": {"type": "array", "items": {"type": "string"}, "description": "Keywords or categories for your thought."}, "axioms_used": {"type": "array", "items": {"type": "string"}, "description": "Principles or axioms applied in your thought."}, "axiomsUsed": {"type": "array", "items": {"type": "string"}, "description": "camelCase alias for axioms_used."}, "assumptions_challenged": {"type": "array", "items": {"type": "string"}, "description": "Assumptions your thought questions or challenges."}, "assumptionsChallenged": {"type": "array", "items": {"type": "string"}, "description": "camelCase alias for assumptions_challenged."}, "session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "generate_summary", "description": "Generate a summary of one thinking session. Content-bearing: summaries derive from stored thought content.", "parameters": {"type": "object", "properties": {"session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "clear_history", "description": "Reset one thinking session by clearing recorded thoughts.", "parameters": {"type": "object", "properties": {"session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "export_session", "description": "Export one thinking session to a JSON file. Content-bearing: exported files include thought text. Parent directories are created automatically.", "parameters": {"type": "object", "required": ["file_path"], "properties": {"file_path": {"type": "string", "description": "Path to save the exported session JSON file."}, "session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "import_session", "description": "Import a previously exported thinking session from a JSON file. Treats imported thought text as inert content.", "parameters": {"type": "object", "required": ["file_path"], "properties": {"file_path": {"type": "string", "description": "Path to the JSON file to import."}, "session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "get_thinking_history", "description": "Read recorded thoughts for one session with bounded pagination. Content-bearing: may return full thought text unless include_full_thoughts=false.", "parameters": {"type": "object", "properties": {"session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "limit": {"type": "integer", "minimum": 1, "maximum": 100, "description": "Maximum thoughts to return."}, "offset": {"type": "integer", "minimum": 0, "description": "Number of thoughts to skip from the start."}, "include_full_thoughts": {"type": "boolean", "description": "Whether to include full thought text. Default true; pass false to receive 120-char snippets."}, "includeFullThoughts": {"type": "boolean", "description": "camelCase alias for include_full_thoughts. Default true."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "get_thinking_status", "description": "Read content-free storage and configuration diagnostics for sequential thinking sessions. Returns storage writability, per-session thought counts and state fingerprints, corrupt-session flags with error strings, backup file names, effectiveConfig.sources labels (flag/env/project_settings/global_settings/config_file/default), and a statusCompleteness block indicating whether the listing was truncated or contained corrupt entries. Use writable=false or sessions[].corrupt=true to diagnose write and parse failures.", "parameters": {"type": "object", "properties": {"piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "sequential_think", "description": "Scaffold a complete staged thinking sequence for a topic in one call. Generates one thought per cognitive stage (Problem Definition through Conclusion) and writes them to the selected session. Use process_thought instead when you want to record your own thoughts step-by-step.", "parameters": {"type": "object", "required": ["topic"], "properties": {"topic": {"type": "string", "description": "The topic or question to think through."}, "num_thoughts": {"type": "integer", "minimum": 3, "maximum": 10, "description": "Number of thoughts to generate (default: 5)."}, "session_id": {"type": "string", "description": "Session to use. Omit for the default session."}, "sessionId": {"type": "string", "description": "camelCase alias for session_id."}, "piMaxBytes": {"type": "integer", "description": "Client-side max bytes override (clamped by config)."}, "piMaxLines": {"type": "integer", "description": "Client-side max lines override (clamped by config)."}}, "additionalProperties": true}, "strict": false}}, {"type": "function", "function": {"name": "subagent", "description": "Delegate to subagents or manage agent definitions.\n\nEXECUTION (use exactly ONE mode):\n• Before executing, use { action: \"list\" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.\n• SINGLE: { agent, task? } - one task; omit task for self-contained agents\n• CHAIN: { chain: [{agent:\"agent-a\"}, {parallel:[{agent:\"agent-b\",count:3}]}] } - sequential pipeline with optional parallel fan-out\n• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)\n• Optional context: { context: \"fresh\" | \"fork\" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise \"fresh\"; inspect agent defaults via { action: \"list\" })\n• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs\n• If { action: \"list\" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help\n\nCHAIN TEMPLATE VARIABLES (use in task strings):\n• {task} - The original task/request from the user\n• {previous} - Text response from the previous step (empty for first step)\n• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)\n\nExample: { chain: [{agent:\"agent-a\", task:\"Analyze {task}\"}, {agent:\"agent-b\", task:\"Plan based on {previous}\"}] }\n\nMANAGEMENT (use action field, omit agent/task/chain/tasks):\n• { action: \"list\" } - discover executable agents/chains\n• { action: \"get\", agent: \"name\" } - full detail; packaged agents use dotted runtime names like \"package.agent\"\n• { action: \"models\", agent?: \"name\" } - show the runtime-loaded builtin subagent model mapping, optionally filtered to one builtin\n• { action: \"create\", config: { name: \"custom-agent\", package: \"code-analysis\", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } }\n• { action: \"update\", agent: \"code-analysis.custom-agent\", config: { package: \"analysis\", ... } } - merge\n• { action: \"delete\", agent: \"code-analysis.custom-agent\" }\n• { action: \"eject\", agent: \"reviewer\", agentScope?: \"user\" | \"project\" } - copy a bundled/package agent to user/project scope as an editable custom file that shadows the original (default scope: user)\n• { action: \"disable\", agent: \"reviewer\", agentScope?: \"user\" | \"project\" } - hide any agent from runtime discovery via a reversible settings override (default scope: user)\n• { action: \"enable\", agent: \"reviewer\", agentScope?: \"user\" | \"project\" } - remove a disabled override and restore discovery\n• { action: \"reset\", agent: \"reviewer\", agentScope?: \"user\" | \"project\" } - delete the scope's custom agent file and/or settings override, restoring the bundled default\n• Use chainName for chain operations; packaged chains also use dotted runtime names\n\nCONTROL:\n• { action: \"status\", id: \"...\" } - inspect an async/background run by id or prefix\n• { action: \"status\", view: \"fleet\" } - read-only active foreground/async fleet view with transcript commands\n• { action: \"status\", id: \"...\", view: \"transcript\", index?: 0, lines?: 80 } - tail a run or child output/session transcript\n• { action: \"interrupt\", id?: \"...\" } - soft-interrupt the current child turn and leave the run paused\n• { action: \"resume\", id: \"...\", message: \"...\", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session\n• { action: \"steer\", id: \"...\", message: \"...\", index?: 0 } - queue non-terminal guidance for a live/queued async Pi child when supported\n• { action: \"append-step\", id: \"...\", chain: [{agent:\"agent-c\", task:\"Use {previous}\"}] } - append one step to the tail of a running async chain\n\nSCHEDULE (opt-in; requires { \"scheduledRuns\": { \"enabled\": true } } in config.json):\n• { action: \"schedule\", agent, task?, schedule: \"+10m\" | \"2030-01-01T09:00:00Z\", scheduleName? } - defer a subagent launch until a future time. Also accepts tasks[] or chain[]. Scheduled runs always launch async with fresh context; they become normal tracked async runs once they fire. Only schedule explicit delayed runs the user asked for.\n• { action: \"schedule-list\" } - list scheduled runs for this session\n• { action: \"schedule-status\", id: \"...\" } - inspect one scheduled run\n• { action: \"schedule-cancel\", id: \"...\" } - cancel a scheduled run before it fires\n\nDIAGNOSTICS:\n• { action: \"doctor\" } - read-only report for runtime paths, discovery, sessions, and intercom\n\nSAFETY-CRITICAL SUBAGENT GUIDANCE:\n• Use { action: \"list\" } before execution and only run executable/non-disabled agents or chains.\n• Keep execution and management separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for list/get/models/create/update/delete/status/interrupt/resume/append-step/doctor.\n• Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait; if this turn must block, use the wait tool. Otherwise continue useful work or respond and let completion notifications arrive.\n• Child-safety boundary: ordinary child subagents are not orchestrators and must not run subagents. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.\n• Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.\n• Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: \"status\", id }. Include output paths and residual risks when reporting results.", "parameters": {"type": "object", "properties": {"agent": {"type": "string", "description": "Agent name (SINGLE mode) or target for management get/update/delete"}, "task": {"type": "string", "description": "Task (SINGLE mode, optional for self-contained agents)"}, "action": {"type": "string", "description": "Management/control action only. Must be omitted for execution mode (single, parallel, or chain)."}, "id": {"type": "string", "description": "Run id or prefix for action='status', action='interrupt', action='resume', action='steer', or action='append-step'."}, "runId": {"type": "string", "description": "Target run ID for action='interrupt', action='resume', action='steer', or action='append-step'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls."}, "dir": {"type": "string", "description": "Async run directory for action='status', action='resume', or action='steer'."}, "index": {"type": "integer", "minimum": 0, "description": "Zero-based child index for actions that target a specific child or transcript."}, "view": {"type": "string", "enum": ["fleet", "transcript"], "description": "Optional status view. Use view='fleet' for a read-only active foreground/async fleet surface, or view='transcript' with id/dir (and optional index) to tail a run transcript."}, "lines": {"type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum transcript lines for action='status', view='transcript'. Defaults to 80."}, "message": {"type": "string", "description": "Follow-up message for action='resume' or non-terminal guidance for action='steer'. Use index to choose a child from multi-child runs."}, "schedule": {"type": "string", "description": "Explicit one-shot schedule for action='schedule'. Only honored when scheduledRuns.enabled is true. Use '+10m' or a future ISO timestamp with timezone; scheduled runs always launch async with fresh context."}, "scheduleName": {"type": "string", "description": "Optional display name for action='schedule'."}, "chainName": {"type": "string", "description": "Chain name for get/update/delete management actions"}, "config": {"anyOf": [{"type": "object", "additionalProperties": true}, {"type": "string"}], "description": "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."}, "tasks": {"type": "array", "items": {"type": "object", "required": ["agent", "task"], "properties": {"agent": {"type": "string"}, "task": {"type": "string"}, "cwd": {"type": "string"}, "count": {"type": "integer", "minimum": 1}, "output": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, "outputMode": {"type": "string", "enum": ["inline", "file-only"]}, "reads": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}]}, "progress": {"type": "boolean"}, "model": {"type": "string"}, "skill": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}, {"type": "string"}]}, "toolBudget": {"type": "object", "required": ["hard"], "properties": {"soft": {"type": "integer", "minimum": 1}, "hard": {"type": "integer", "minimum": 1}, "block": {"anyOf": [{"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, {"type": "string", "enum": ["*"]}]}}, "additionalProperties": false}, "acceptance": {"anyOf": [{"type": "string", "enum": ["auto", "none", "attested", "checked", "verified", "reviewed"]}, {"type": "boolean", "enum": [false]}, {"type": "object", "additionalProperties": true}]}}}, "description": "PARALLEL mode: [{agent, task, count?, output?, outputMode?, reads?, progress?}, ...]"}, "concurrency": {"type": "integer", "minimum": 1, "description": "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4."}, "worktree": {"type": "boolean", "description": "Create isolated git worktrees for parallel tasks; requires clean git state."}, "chain": {"type": "array", "items": {"type": "object", "properties": {"agent": {"type": "string"}, "task": {"type": "string"}, "phase": {"type": "string"}, "label": {"type": "string"}, "as": {"type": "string"}, "outputSchema": {"type": "object", "additionalProperties": true}, "cwd": {"type": "string"}, "output": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, "outputMode": {"type": "string", "enum": ["inline", "file-only"]}, "reads": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}]}, "progress": {"type": "boolean"}, "skill": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}, {"type": "string"}]}, "model": {"type": "string"}, "toolBudget": {"type": "object", "required": ["hard"], "properties": {"soft": {"type": "integer", "minimum": 1}, "hard": {"type": "integer", "minimum": 1}, "block": {"anyOf": [{"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, {"type": "string", "enum": ["*"]}]}}, "additionalProperties": false}, "acceptance": {"anyOf": [{"type": "string", "enum": ["auto", "none", "attested", "checked", "verified", "reviewed"]}, {"type": "boolean", "enum": [false]}, {"type": "object", "additionalProperties": true}]}, "parallel": {"anyOf": [{"type": "array", "items": {"type": "object", "required": ["agent"], "properties": {"agent": {"type": "string"}, "task": {"type": "string"}, "phase": {"type": "string"}, "label": {"type": "string"}, "as": {"type": "string"}, "outputSchema": {"type": "object", "additionalProperties": true}, "cwd": {"type": "string"}, "count": {"type": "integer", "minimum": 1}, "output": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, "outputMode": {"type": "string", "enum": ["inline", "file-only"]}, "reads": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}]}, "progress": {"type": "boolean"}, "skill": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}, {"type": "string"}]}, "model": {"type": "string"}, "toolBudget": {"type": "object", "required": ["hard"], "properties": {"soft": {"type": "integer", "minimum": 1}, "hard": {"type": "integer", "minimum": 1}, "block": {"anyOf": [{"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, {"type": "string", "enum": ["*"]}]}}, "additionalProperties": false}, "acceptance": {"anyOf": [{"type": "string", "enum": ["auto", "none", "attested", "checked", "verified", "reviewed"]}, {"type": "boolean", "enum": [false]}, {"type": "object", "additionalProperties": true}]}}}, "minItems": 1}, {"type": "object", "required": ["agent"], "properties": {"agent": {"type": "string"}, "task": {"type": "string"}, "phase": {"type": "string"}, "label": {"type": "string"}, "outputSchema": {"type": "object", "additionalProperties": true}, "cwd": {"type": "string"}, "output": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, "outputMode": {"type": "string", "enum": ["inline", "file-only"]}, "reads": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}]}, "progress": {"type": "boolean"}, "skill": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}, {"type": "string"}]}, "model": {"type": "string"}, "toolBudget": {"type": "object", "required": ["hard"], "properties": {"soft": {"type": "integer", "minimum": 1}, "hard": {"type": "integer", "minimum": 1}, "block": {"anyOf": [{"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, {"type": "string", "enum": ["*"]}]}}, "additionalProperties": false}, "acceptance": {"anyOf": [{"type": "string", "enum": ["auto", "none", "attested", "checked", "verified", "reviewed"]}, {"type": "boolean", "enum": [false]}, {"type": "object", "additionalProperties": true}]}}, "additionalProperties": false}]}, "expand": {"type": "object", "required": ["from"], "properties": {"from": {"type": "object", "required": ["output", "path"], "properties": {"output": {"type": "string"}, "path": {"type": "string"}}, "additionalProperties": false}, "item": {"type": "string"}, "key": {"type": "string"}, "maxItems": {"type": "integer", "minimum": 0}, "onEmpty": {"type": "string", "enum": ["skip", "fail"]}}, "additionalProperties": false}, "collect": {"type": "object", "required": ["as"], "properties": {"as": {"type": "string"}, "outputSchema": {"type": "object", "additionalProperties": true}}, "additionalProperties": false}, "concurrency": {"type": "number"}, "failFast": {"type": "boolean"}, "worktree": {"type": "boolean"}}, "additionalProperties": false}, "description": "CHAIN mode: sequential steps; each result becomes {previous}. append-step takes one tail step and may use {chain_dir}/{outputs.name}."}, "context": {"type": "string", "enum": ["fresh", "fork"], "description": "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh."}, "chainDir": {"type": "string", "description": "Persistent chain artifact directory; defaults to user-scoped temp storage."}, "async": {"type": "boolean", "description": "Run in background (default: false, or per config)"}, "timeoutMs": {"type": "integer", "minimum": 1, "description": "Optional run-level timeout in ms for foreground and async/background runs. Alias of maxRuntimeMs."}, "maxRuntimeMs": {"type": "integer", "minimum": 1, "description": "Alias of timeoutMs for optional run-level timeout in foreground and async/background runs."}, "turnBudget": {"type": "object", "required": ["maxTurns"], "properties": {"maxTurns": {"type": "integer", "minimum": 1}, "graceTurns": {"type": "integer", "minimum": 0}}, "additionalProperties": false, "description": "Optional assistant-turn budget. At maxTurns the child is asked to wrap up; after graceTurns additional assistant turns it is aborted and partial output is returned."}, "toolBudget": {"type": "object", "required": ["hard"], "properties": {"soft": {"type": "integer", "minimum": 1}, "hard": {"type": "integer", "minimum": 1}, "block": {"anyOf": [{"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, {"type": "string", "enum": ["*"]}]}}, "additionalProperties": false, "description": "Optional child tool-call budget. soft nudges the child; after hard, block tools (default read/grep/find/ls, or '*' for all tools) are blocked so the child can finalize."}, "agentScope": {"type": "string", "description": "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)"}, "cwd": {"type": "string"}, "artifacts": {"type": "boolean", "description": "Write debug artifacts (default: true)"}, "includeProgress": {"type": "boolean", "description": "Include full progress in result (default: false)"}, "share": {"type": "boolean", "description": "Upload session to GitHub Gist for sharing (default: false)"}, "sessionDir": {"type": "string", "description": "Directory to store session logs (default: temp; enables sessions even if share=false)"}, "clarify": {"type": "boolean", "description": "Show TUI to preview/edit before execution. Explicit clarify: true keeps the run foreground for the clarify UI; omitted clarify can still run in the background when async: true is set."}, "control": {"type": "object", "properties": {"enabled": {"type": "boolean"}, "needsAttentionAfterMs": {"type": "integer", "minimum": 1}, "activeNoticeAfterMs": {"type": "integer", "minimum": 1}, "activeNoticeAfterTurns": {"type": "integer", "minimum": 1}, "activeNoticeAfterTokens": {"type": "integer", "minimum": 1}, "failedToolAttemptsBeforeAttention": {"type": "integer", "minimum": 1}, "notifyOn": {"type": "array", "items": {"type": "string", "enum": ["active_long_running", "needs_attention"]}}, "notifyChannels": {"type": "array", "items": {"type": "string", "enum": ["event", "async", "intercom"]}}}}, "output": {"anyOf": [{"type": "string"}, {"type": "boolean"}], "description": "Output file for single agent (string), or false to disable. Relative paths resolve against cwd."}, "outputMode": {"type": "string", "enum": ["inline", "file-only"], "description": "Return saved output inline (default) or only a concise file reference. file-only requires output to be a path."}, "skill": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}, {"type": "string"}], "description": "Skill name(s) to make available (comma-separated), array of strings, or boolean (false disables, true uses default)"}, "model": {"type": "string", "description": "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')"}, "acceptance": {"anyOf": [{"type": "string", "enum": ["auto", "none", "attested", "checked", "verified", "reviewed"]}, {"type": "boolean", "enum": [false]}, {"type": "object", "additionalProperties": true}], "description": "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands."}}}, "strict": false}}, {"type": "function", "function": {"name": "wait", "description": "Block until background (async) subagent runs started in this session finish, then return.\n\nUse this after launching async subagents when you have no independent work left and must not end your turn — for example inside a skill that has to run to completion, or any non-interactive run (`pi -p ...`) where the whole task is a single turn and ending it would abandon the still-running children.\n\n• { } — return as soon as the FIRST active run finishes (default). Ideal for a rolling fleet: launch N, wait, spawn a replacement for the one that finished, wait again — keeping N in flight.\n• { all: true } — block until EVERY active run in this session is finished.\n• { id: \"...\" } — wait for one specific run (id or prefix) to finish.\n• { timeoutMs: 600000 } — stop waiting after N ms (the runs keep going regardless; default 30 min)\n\nwait also returns when a run needs attention (a child that went idle or blocked for a decision), not only on completion — so a stuck child never stalls the loop; the summary names the run(s) to inspect/nudge/resume/interrupt. It wakes the instant a completion or control event arrives (subscribed to Pi's event bus, with a poll fallback that reconciles crashed runners), keeps the turn alive for normal notification delivery, and resolves early if the turn is aborted.", "parameters": {"type": "object", "properties": {"id": {"type": "string", "description": "Run id or prefix to wait for one specific run. Omit to wait across every active async run started in this session."}, "all": {"type": "boolean", "description": "Wait for ALL active runs to finish. Default false: return as soon as the first run finishes, so a fleet manager can spawn a replacement and wait again. Ignored when id targets a single run."}, "timeoutMs": {"type": "integer", "minimum": 1, "description": "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to 1800000 (30 minutes)."}}}, "strict": false}}, {"type": "function", "function": {"name": "memory_search", "description": "Search persistent memory for facts, preferences, and project patterns the user has established across sessions.", "parameters": {"type": "object", "required": ["query"], "properties": {"query": {"type": "string", "description": "Search query"}, "limit": {"type": "number", "description": "Max results (default 10)"}}}, "strict": false}}, {"type": "function", "function": {"name": "memory_remember", "description": "Store a fact, preference, or lesson in persistent memory. Use dotted keys like pref.editor, project.rosie.lang, tool.sed.usage. For corrections, use type='lesson'.", "parameters": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string", "description": "'fact' for key-value, 'lesson' for a correction"}, "key": {"type": "string", "description": "Dotted key for facts (e.g. pref.commit_style)"}, "value": {"type": "string", "description": "Value for facts"}, "rule": {"type": "string", "description": "Rule text for lessons"}, "category": {"type": "string", "description": "Category for lessons (default: general)"}, "negative": {"type": "boolean", "description": "True if this is something to AVOID"}}}, "strict": false}}, {"type": "function", "function": {"name": "memory_forget", "description": "Remove a fact or lesson from persistent memory.", "parameters": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string"}, "key": {"type": "string", "description": "Key for facts"}, "id": {"type": "string", "description": "ID for lessons"}}}, "strict": false}}, {"type": "function", "function": {"name": "memory_lessons", "description": "List learned corrections and lessons from past sessions.", "parameters": {"type": "object", "properties": {"category": {"type": "string", "description": "Filter by category"}, "limit": {"type": "number", "description": "Max results (default 50)"}}}, "strict": false}}, {"type": "function", "function": {"name": "memory_stats", "description": "Show memory statistics — how many facts, lessons, and events are stored.", "parameters": {"type": "object", "properties": {}}, "strict": false}}, {"type": "function", "function": {"name": "web_search", "description": "Search the web using OpenAI, Brave, Parallel, Tavily, Exa, Perplexity, or Gemini. Returns an AI-synthesized answer with source citations. OpenAI web_search uses a Codex subscription or OpenAI API key. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to \"none\" to skip curation or \"auto-summary\" for a model-generated summary without the browser curator. Provider auto-selects: OpenAI when suitable and available, then Exa, Brave, Parallel, Tavily, Perplexity, Gemini API, then Gemini Web.", "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "Single search query. For research tasks, prefer 'queries' with multiple varied angles instead."}, "queries": {"type": "array", "items": {"type": "string"}, "description": "Multiple queries searched in sequence, each returning its own synthesized answer. Prefer this for research — vary phrasing, scope, and angle across 2-4 queries to maximize coverage. Good: ['React vs Vue performance benchmarks 2026', 'React vs Vue developer experience comparison', 'React ecosystem size vs Vue ecosystem']. Bad: ['React vs Vue', 'React vs Vue comparison', 'React vs Vue review'] (too similar, redundant results)."}, "numResults": {"type": "number", "description": "Results per query (default: 5, max: 20)"}, "includeContent": {"type": "boolean", "description": "Fetch full page content (async)"}, "recencyFilter": {"type": "string", "enum": ["day", "week", "month", "year"], "description": "Filter by recency"}, "domainFilter": {"type": "array", "items": {"type": "string"}, "description": "Limit to domains (prefix with - to exclude)"}, "provider": {"type": "string", "enum": ["auto", "openai", "brave", "parallel", "tavily", "exa", "perplexity", "gemini", "firecrawl"], "description": "Search provider (default: auto)"}, "workflow": {"type": "string", "enum": ["none", "summary-review", "auto-summary"], "description": "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default), auto-summary = generate summary without opening curator"}}}, "strict": false}}, {"type": "function", "function": {"name": "fetch_content", "description": "Fetch URL(s) and extract readable content as markdown. Supports YouTube video transcripts (with thumbnail), GitHub repository contents, and local video files (with frame thumbnail). Video frames can be extracted via timestamp/range or sampled across the entire video with frames alone. Falls back to Gemini for pages that block bots or fail Readability extraction. For YouTube and video files: ALWAYS pass the user's specific question via the prompt parameter — this directs the AI to focus on that aspect of the video, producing much better results than a generic extraction. Content is always stored and can be retrieved with get_search_content.", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "Single URL to fetch"}, "urls": {"type": "array", "items": {"type": "string"}, "description": "Multiple URLs (parallel)"}, "forceClone": {"type": "boolean", "description": "Force cloning large GitHub repositories that exceed the size threshold"}, "prompt": {"type": "string", "description": "Question or instruction for video analysis (YouTube and video files). Pass the user's specific question here — e.g. 'describe the book shown at the advice for beginners section'. Without this, a generic transcript extraction is used which may miss what the user is asking about."}, "timestamp": {"type": "string", "description": "Extract video frame(s) at a timestamp or time range. Single: '1:23:45', '23:45', or '85' (seconds). Range: '23:41-25:00' extracts evenly-spaced frames across that span (default 6). Use frames with ranges to control density; single+frames uses a fixed 5s interval. YouTube requires yt-dlp + ffmpeg; local videos require ffmpeg. Use a range when you know the approximate area but not the exact moment — you'll get a contact sheet to visually identify the right frame."}, "frames": {"type": "integer", "minimum": 1, "maximum": 12, "description": "Number of frames to extract. Use with timestamp range for custom density, with single timestamp to get N frames at 5s intervals, or alone to sample across the entire video. Requires yt-dlp + ffmpeg for YouTube, ffmpeg for local video."}, "model": {"type": "string", "description": "Override the Gemini model for video/YouTube analysis (e.g. 'gemini-2.5-flash', 'gemini-3-flash-preview'). Defaults to config or gemini-3-flash-preview."}}}, "strict": false}}, {"type": "function", "function": {"name": "get_search_content", "description": "Retrieve full content from a previous web_search or fetch_content call.", "parameters": {"type": "object", "required": ["responseId"], "properties": {"responseId": {"type": "string", "description": "The responseId from web_search or fetch_content"}, "query": {"type": "string", "description": "Get content for this query (web_search)"}, "queryIndex": {"type": "number", "description": "Get content for query at index"}, "url": {"type": "string", "description": "Get content for this URL"}, "urlIndex": {"type": "number", "description": "Get content for URL at index"}}}, "strict": false}}, {"type": "function", "function": {"name": "Grep", "description": "Search for a regex pattern in file contents. Returns matching lines with file path and line number. Use the include parameter to filter by file type.", "parameters": {"type": "object", "required": ["pattern"], "properties": {"pattern": {"type": "string", "description": "Regex pattern to search for in file contents"}, "path": {"type": "string", "description": "Directory or file to search. Defaults to current working directory."}, "include": {"type": "string", "description": "Glob pattern to filter which files are searched (e.g. *.ts, **/*.md)"}}}, "strict": false}}, {"type": "function", "function": {"name": "Glob", "description": "Find files matching a glob pattern. Returns a list of matching file paths sorted by modification time (newest first).", "parameters": {"type": "object", "required": ["pattern"], "properties": {"pattern": {"type": "string", "description": "Glob pattern to match files (e.g. **/*.ts, src/**/*.json)"}, "path": {"type": "string", "description": "Directory to search within. Defaults to current working directory."}}}, "strict": false}}, {"type": "function", "function": {"name": "LS", "description": "List the contents of a directory, including hidden files.", "parameters": {"type": "object", "required": ["path"], "properties": {"path": {"type": "string", "description": "Directory path to list"}}}, "strict": false}}, {"type": "function", "function": {"name": "AskUserQuestion", "description": "Ask the user a question and wait for their response. Use when you need clarification or a decision before proceeding. Mirrors Claude Code's AskUserQuestion tool.", "parameters": {"type": "object", "required": ["question", "options"], "properties": {"question": {"type": "string", "description": "The question to present to the user"}, "options": {"type": "array", "items": {"type": "string"}, "description": "Predefined options for the user to choose from. User can also type a custom response."}}}, "strict": false}}, {"type": "function", "function": {"name": "WebFetch", "description": "Fetch content from a URL and return it as clean markdown. Mirrors Claude Code's WebFetch tool. Uses Jina Reader (r.jina.ai) for clean extraction — no API key required.", "parameters": {"type": "object", "required": ["url"], "properties": {"url": {"type": "string", "description": "The URL to fetch content from"}, "prompt": {"type": "string", "description": "Optional hint about what information to extract (noted in result header, model uses it for focus)"}}}, "strict": false}}, {"type": "function", "function": {"name": "WebSearch", "description": "Perform a web search and return results. Mirrors Claude Code's WebSearch tool. Uses Brave Search API — requires BRAVE_API_KEY environment variable.", "parameters": {"type": "object", "required": ["query"], "properties": {"query": {"type": "string", "description": "The search query"}, "count": {"type": "number", "description": "Number of results to return (default: 5, max: 20)"}}}, "strict": false}}, {"type": "function", "function": {"name": "Skill", "description": "Execute a skill within the main conversation. Mirrors Claude Code's Skill tool. Loads the skill's SKILL.md content and returns it so the model can follow its instructions.", "parameters": {"type": "object", "required": ["name"], "properties": {"name": {"type": "string", "description": "Name of the skill to execute (must match a discovered skill name)"}, "arguments": {"type": "string", "description": "Optional arguments or context to pass to the skill"}}}, "strict": false}}, {"type": "function", "function": {"name": "EnterPlanMode", "description": "Switch to plan mode for safe read-only codebase analysis. Mirrors Claude Code's EnterPlanMode tool. Restricts available tools to read-only operations. Call ExitPlanMode when done planning.", "parameters": {"type": "object", "properties": {}}, "strict": false}}, {"type": "function", "function": {"name": "ExitPlanMode", "description": "Present a plan for user approval and exit plan mode. Mirrors Claude Code's ExitPlanMode tool. Requires user approval — the user can approve, reject, or request refinement.", "parameters": {"type": "object", "properties": {"plan": {"type": "string", "description": "The plan to present to the user for approval. If omitted, the plan is extracted from the conversation."}}}, "strict": false}}, {"type": "function", "function": {"name": "Agent", "description": "Invoke a named agent with a task. Mirrors Claude Code's Agent() tool (formerly Task()). The description field is matched to a pi agent by name. Requires pi-subagents: pi install npm:pi-subagents", "parameters": {"type": "object", "required": ["description", "prompt"], "properties": {"description": {"type": "string", "description": "Name or short description of the agent to invoke (matched to a pi agent by name)"}, "prompt": {"type": "string", "description": "The full task or instructions to pass to the agent"}}}, "strict": false}}, {"type": "function", "function": {"name": "monitor", "description": "Run a shell command in the background. Each stdout line becomes an event that wakes the conversation. Stderr is captured to a file (read with monitor_read_stderr). Use for tailing logs, polling CI, watching dev servers.", "parameters": {"type": "object", "required": ["description", "command"], "properties": {"description": {"type": "string", "description": "Short label shown with every notification (e.g. 'errors in deploy.log').", "minLength": 1, "maxLength": 80}, "command": {"type": "string", "description": "Shell command whose stdout lines become events.", "minLength": 1}, "timeout_ms": {"type": "integer", "minimum": 1000, "maximum": 3600000, "default": 300000, "description": "Auto-kill after N ms. Default 300000 (5 min), max 3600000 (1 hr). Ignored when persistent=true."}, "persistent": {"type": "boolean", "default": false, "description": "If true, runs for the session lifetime. Stop manually with monitor_stop."}}}, "strict": false}}, {"type": "function", "function": {"name": "monitor_stop", "description": "Stop a running monitor by id. Equivalent of Claude Code's TaskStop for monitors.", "parameters": {"type": "object", "required": ["monitorId"], "properties": {"monitorId": {"type": "string", "description": "The monitor id returned by `monitor`."}}}, "strict": false}}, {"type": "function", "function": {"name": "monitor_list", "description": "List all running monitors with their status and recent activity.", "parameters": {"type": "object", "properties": {}}, "strict": false}}, {"type": "function", "function": {"name": "monitor_read_stderr", "description": "Read the last N lines of a monitor's stderr file. Pulls on demand — does not wake the conversation. Monitor streams stdout only; stderr, exit codes, and silent crashes live here.", "parameters": {"type": "object", "required": ["monitorId"], "properties": {"monitorId": {"type": "string"}, "tail": {"type": "integer", "minimum": 1, "maximum": 10000, "default": 200}}}, "strict": false}}, {"type": "function", "function": {"name": "subagent_supervisor", "description": "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.", "parameters": {"type": "object", "required": ["action"], "properties": {"action": {"type": "string", "enum": ["list", "send", "ask", "reply", "pending", "status"]}, "to": {"type": "string"}, "message": {"type": "string"}, "replyTo": {"type": "string"}}, "additionalProperties": false}, "strict": false}}, {"type": "function", "function": {"name": "intercom", "description": "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests.", "parameters": {"type": "object", "required": ["action"], "properties": {"action": {"type": "string", "enum": ["list", "send", "ask", "reply", "pending", "status"]}, "to": {"type": "string"}, "message": {"type": "string"}, "replyTo": {"type": "string"}}, "additionalProperties": false}, "strict": false}}]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment