Repository navigation
feat: authoring skill, script iterate loop, and pi 1.3 APIs for workflows - #62
Merged
Merged
Conversation
Finders can return more candidates than the per-lens cap their prompt states. The pipeline sliced the excess off before counting, so neither the log nor the dropped counter mentioned it. Count every returned candidate, log the overflow per lens, and include it in the dropped counter and stats while keeping the duplicate/out-of-scope log accurate.
buildClaimCandidates keeps the MAX_VERIFICATION_CLAIMS most important claims and the workflow logged only how many it selected. Return the count the cap removed alongside the candidates and log it when it is non-zero.
…alue pi's stock system prompt frames a chat with a person, so text-mode subagents could greet, narrate, or offer further help in the value the workflow receives. Append a return-value instruction to every subagent's system prompt through appendSystemPromptOverride, which keeps pi's discovery of the user's APPEND_SYSTEM.md (appendSystemPrompt would replace it). The system prompt is part of resume identity, so journals written by 1.1.0 or earlier miss once.
Press s twice on a running agent in the inspector's Agents tab to abort only that agent's session. The agent() call rejects with a recoverable WorkflowAgentSkippedError, so parallel()/pipeline() return a null slot while a direct await sees the error. A skipped agent is never restarted by agentRetries or journaled for replay, and its spent usage still counts. Rows gain a skipped status across the tracker, widget, inspector and persisted run records. Live background runs can be skipped from their live inspector.
…delines The host model now reads one authoring reference before writing an inline script: the script contract, the injected api, why fan-out uses api.parallel rather than Promise.all and defaults to api.pipeline, quality patterns, budget loops, worktree edits, resume, and saving a reusable workflow. The workflow tool's guidelines keep only the opt-in rule, the pointer to the skill (by path), the target and Type rules, profiles, the worktree return shape, host MCP reach, and the run-mode flags. The adaptive multi-pass guidance moves from dynamax.ts into the skill.
…mpt section
WorkflowMeta gains whenToUse and phases entries { title, detail?, profile? };
parseWorkflowMeta validates and keeps them for module and inline meta alike,
and every built-in workflow now says when to use it.
A phase profile routes an agent that passes that phase title explicitly and
sets neither model nor profile (agent model > agent profile > phase profile >
host). The global phase() never selects one, since parallel branches race on it.
Runs that can call the workflow tool get a <workflows> system prompt section
listing each discovered workflow by name, description and whenToUse. It is
built once per session so the text is identical on every turn, and replaces
the hardcoded workflow names the tool guidelines used to carry. Dynamax and
the catalog share one forced-prompt fallback helper.
… no argument Sticky Dynamax now sends its own <dynamax> text: orchestrate every substantive task with the workflow tool by default, scout inline first, run one single-phase workflow per phase and read each result before the next, lean toward adversarial verification, and skip workflows only for conversational turns and trivial or already-verified edits. The one-shot token keeps the one-request reminder. before_agent_start also fires for runs a custom message starts (pi 1.2+): sticky mode covers those runs, while a pending one-shot is consumed only by a user prompt run. /workflow:dynamax with no argument toggles sticky mode instead of printing usage; on, off and status keep working.
/workflow:size [small|medium|large|off] [--user|--project] shows or sets how large the host should make each workflow it authors: under 5, 10 (default) or 25 agents, or no guideline. It persists like the model profiles, in workflow-settings.json under the agent dir with a project .pi/ override, and renders as one line closing the <workflows> section. Changing it rebuilds the session's catalog once. The maxAgents run limit is unchanged and stays the separate hard cap. The model-profile file helpers move to config-file.ts so both configs share them.
A standalone +500k, +1.5m, +2M or +250000 token in a typed prompt (not one an extension sends) sets an output-token pool for that turn, cleared at agent_end. Values under 1,000 are ignored so a +1 in prose never caps a run. A workflow tool call without its own budget runs with the pool's remainder, clamped to the budget range, and each synchronous run's output tokens are subtracted when it finishes. A spent pool turns further calls into a tool error. Background runs receive the remainder but do not draw the pool down. The pool is exposed through the Dynamax handle.
…Dynamax completes When the workflow tool starts a background run while Dynamax is active for the session (sticky, or the one-shot turn), the run's origin records wakeOnComplete and its completion is delivered with triggerTurn: true, so the agent wakes to act on the result. The flag is persisted with the run record, so a delivery after a restart still wakes it. Other background runs keep the silent delivery. Dynamax exposes isActive(ctx) for the tool to read.
Every inline run saves its script to .pi/.workflow-runs/<runId>.script.js,
outside the redacted run record, and the engine now writes a '*' .gitignore
wherever it creates that directory. The workflow tool takes scriptPath
(cwd-relative, realpath-guarded); with resumeFromRunId it implies
edited-workflow resume. api.workflow() accepts { scriptPath } again with the
same guard. Results and failed runs name their script and journal, background
starts name the script, and /workflow:runs restarts and resumes inline runs
from their persisted script.
discoverWorkflows takes an optional projectDir and loads inline-format *.js scripts from <project>/.pi/workflows (only when ctx.isProjectTrusted()) and from <agentDir>/workflows, compiled with the injected Type. They resolve by name with selfContained file provenance, so resume trusts their content fingerprint and /workflow:runs relaunches them. Precedence: built-ins, project scripts, extension .ts drop-ins, user scripts, user .ts drop-ins.
reachesTools lets pi's MCP extension wait for the servers a workflow call may reach before it runs, so a synchronous run early in a session no longer races MCP tool registration. Background calls reach nothing; inline scripts and scriptPath files reach the mcp__ tools they name, or every server when they use external-search hints or compose a sub-workflow; named workflows and unreadable script files reach every server.
The workflow tool's args and api.workflow(ref, args) accept a string or any JSON value. api.args stays a string (JSON text for non-strings) so existing workflows keep working, and api.input carries the value verbatim (the string for slash commands). JSON args follow the string rules: never persisted, and a run that had them is not relaunchable.
…age, as the return value
…d slots cannot deadlock pi's default parallel tool execution runs every call's tool_call hook before executing any, and the slot queue waits in tool_call. One message with more codemode calls than slots left the calls holding slots unable to run while the next waited for one, hanging the agent until agentTimeoutMs and starving every other codemode agent. Codemode subagent sessions now set agent.toolExecution to sequential, so each call frees its slot before the next asks for one.
pi 1.3 emits agent_end per agent loop, and one run continues in fresh loops after a retryable provider error, overflow compaction, or a queued follow-up. Clearing on agent_end left the rest of that run, whose prompt still carries the dynamax section, with no +N pool and no wake-on-complete for background calls. Clear on agent_settled, which pi fires once when the run settles.
Subagent sessions load this extension, and their prompts arrive as interactive input, so a subagent prompt naming src/dynamax.ts armed a one-shot and got a <dynamax> section permitting a workflow tool the session withholds. Skip the section, without consuming the one-shot, when the run's selectedTools lack the workflow tool, as the <workflows> catalog already does.
…very its run files pi sends the model only a custom message's content, never its details, so a run started under Dynamax woke the agent with a 500-character summary and no Script/Journal lines. A woken delivery now renders the result with the synchronous tool path's formatter (or the full retained state message of a failed, stopped or paused run); silent deliveries keep the summary. Both name the run's record, script and journal.
Saved scripts in <cwd>/.pi/workflows and <agentDir>/workflows were cached process-wide with the built-ins and .ts drop-ins. The <workflows> catalog warms that cache on a session's first prompt, so a script the host saved mid-session was an unknown workflow by name, and an edited one silently ran its old compiled module. Saved scripts are now read and compiled on every discovery; built-ins and .ts drop-ins keep the cache until --refresh. Precedence is unchanged. A broken script's skip note is reported once per distinct failure rather than on every lookup. The per-session catalog stays cached for prompt-cache stability, so a new script joins it from the next session.
Only .pi/.workflow-runs/ is excluded from the repository state shared read-only replay binds to. Editing a script copy elsewhere in the repository, or a saved .pi/workflows script, changes that state, so every shared read-only call reruns live although the docs promised reuse. The skill's resume section, USAGE's scriptPath paragraph, and the tool's scriptPath description now say to edit the persisted script in place.
… text The authoring skill's api table predated JSON workflow args and scriptPath sub-workflows: it had no input row, described args as the raw string, and listed workflow(name) only. It now matches WorkflowApi and workflow-input.ts, and the planning tip passes a work list as a JSON args value read from api.input instead of a stringified string.
A cloned repository could commit a forged <id>.run.json and <id>.script.js under .pi/.workflow-runs/, and /workflow:runs restart or resume would compile and run that script in-process. The engine now stores an HMAC-SHA256 of the run id and source fingerprint under a per-install key in pi's agent directory, and relaunch verifies it before reading the script, then requires the script's fingerprint to match the record. Unsealed inline records, including those written before this change, are not relaunchable, and inline runs are labelled as such in the runs list and browser.
…read only bounded regular files pi calls reachesTools synchronously from its MCP tool_call handler, and the workflow tool's reader ran readFileSync on the raw scriptPath before any containment check, so a FIFO, a terminal or /dev/zero froze the whole pi process. A scriptPath now reaches every MCP server unread. The execute path also stats the resolved file and refuses anything that is not a regular file of at most 1 MiB before reading it.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
… resume is scheduled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the engine closer to how Claude Code's dynamic workflows feel in practice: better guidance for the host model, a tighter author-iterate loop, and pi 1.2–1.3 APIs. Builds against pi 1.3.0.
What changes
Host model guidance
workflow-authoringskill: the fullapireference,api.parallelinstead ofPromise.all(null slots, not a rejected stage),pipeline()as the default plus the barrier test, quality patterns (adversarial verify, judge panel, loop-until-dry, completeness critic, no silent caps), and "scout inline, then one single-phase workflow per turn". The tool's guidelines are trimmed to the opt-in rule and hard constraints. Across 84 host-written scripts in real sessions, none usedpipeline(), andPromise.allhad replacedapi.paralleloutside this repo.<workflows>catalog section built from discovery (name, description, newmeta.whenToUse), stable per session for prompt caching. Meta also gains an optionalphases[].profile.dynamaxtoken stays a one-shot permission, and/workflow:dynamaxwith no argument toggles sticky mode./workflow:size small|medium|large|off(default medium, under 10 agents) writes a guideline line.maxAgentsstays the separate hard cap.+500k-style directive: a hard per-turn cap on workflow runs (an explicitbudgetcan only lower it), drawn down by synchronous runs and cleared when the run settles.Iterate loop
.pi/.workflow-runs/<id>.script.js(the dir gets a*.gitignore). Results name the script and journal. NewscriptPathon the tool and inapi.workflow();scriptPath+resumeFromRunIdaccepts script edits./workflow:runscan restart and resume inline runs. Runs of inline and saved scripts relaunch only under a seal this install wrote (HMAC under a per-install key in the agent dir) and only while the script matches what ran, so a cloned repo cannot plant a run whose restart executes its own code..jsworkflows in.pi/workflows/(only in projects pi trusts; pi treats a folder whose only pi resource is.pi/workflowsas trusted) and~/.pi/agent/workflows/, compiled like inline scripts, so they avoid the.tstypebox hazard. They are reread on every lookup and must be bounded regular files.args:api.inputholds the verbatim value, andapi.argsstays a string.Subagents and pi APIs
APPEND_SYSTEM.md.reachesToolson the workflow tool, so pi's MCP extension waits for the servers a run needs. It never reads files.triggerTurn), with the full result and the run files. Resumes, restarts and usage-limit auto-resumes inherit this.nullslot, is not journaled, and is not retried).How it was built and checked
bun run typecheckclean;bun run test669 pass, 0 fail (603 before). The suite now runs against a throwaway pi agent dir (bunfig.tomlpreload).Notes
/workflow:runs.