Skip to content

feat: authoring skill, script iterate loop, and pi 1.3 APIs for workflows - #62

Merged
timbrinded merged 46 commits into
masterfrom
feat/pi-codemode-updates
Oct 20, 2026
Merged

timbrinded merged 46 commits into
masterfrom
feat/pi-codemode-updates

Conversation

@timbrinded

@timbrinded timbrinded commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

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

  • New workflow-authoring skill: the full api reference, api.parallel instead of Promise.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 used pipeline(), and Promise.all had replaced api.parallel outside this repo.
  • <workflows> catalog section built from discovery (name, description, new meta.whenToUse), stable per session for prompt caching. Meta also gains an optional phases[].profile.
  • Sticky Dynamax is now a standing default ("orchestrate every substantive task, one workflow per phase"). The dynamax token stays a one-shot permission, and /workflow:dynamax with no argument toggles sticky mode.
  • /workflow:size small|medium|large|off (default medium, under 10 agents) writes a guideline line. maxAgents stays the separate hard cap.
  • +500k-style directive: a hard per-turn cap on workflow runs (an explicit budget can only lower it), drawn down by synchronous runs and cleared when the run settles.

Iterate loop

  • Inline scripts are saved as .pi/.workflow-runs/<id>.script.js (the dir gets a * .gitignore). Results name the script and journal. New scriptPath on the tool and in api.workflow(); scriptPath + resumeFromRunId accepts script edits.
  • /workflow:runs can 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.
  • Saved .js workflows in .pi/workflows/ (only in projects pi trusts; pi treats a folder whose only pi resource is .pi/workflows as trusted) and ~/.pi/agent/workflows/, compiled like inline scripts, so they avoid the .ts typebox hazard. They are reread on every lookup and must be bounded regular files.
  • Optional JSON tool args: api.input holds the verbatim value, and api.args stays a string.

Subagents and pi APIs

  • Subagents are told their output is a return value (separate wording for text and schema agents), appended after the user's APPEND_SYSTEM.md.
  • Codemode: pi 1.3 throws past 2 live codemode workers per process. Subagent codemode calls now queue for those 2 slots, and codemode agents run their tool calls one at a time so the queue cannot deadlock.
  • reachesTools on the workflow tool, so pi's MCP extension waits for the servers a run needs. It never reads files.
  • A background run started while Dynamax is active wakes the host when it completes (triggerTurn), with the full result and the run files. Resumes, restarts and usage-limit auto-resumes inherit this.
  • Inspector: skip a running agent (it becomes a recoverable null slot, is not journaled, and is not retried).
  • Advisory and research workflows report what their caps drop, and research's report lists the claims left unverified.

How it was built and checked

  • Implemented in five isolated worktree groups, integrated here, then reviewed twice. First round: four lenses (integration, resume and persistence, host prompt, safety), each checked by an independent verifier told to refute its findings. 14 of 18 findings were confirmed (4 refuted as intended behaviour) and are fixed in this branch. Each code fix has a regression test that fails without the fix. Second round (on this PR): an xhigh code review, a thermo-nuclear quality review, a Codesavers pass and a scope review, each checked by a verifier; the confirmed fixes and cleanups are in the last ten commits.
  • bun run typecheck clean; bun run test 669 pass, 0 fail (603 before). The suite now runs against a throwaway pi agent dir (bunfig.toml preload).

Notes

  • Codemode agents trade concurrency inside a script for deadlock safety: their scripts' tool calls also run one at a time.
  • Saved scripts persisted before this branch carry no seal, so they are not relaunchable from /workflow:runs.
  • Journals from earlier versions miss once on resume, because the subagent system prompt changed.

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.
…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.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 11:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-20T12:04:59.742473Z 003adc6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@timbrinded
timbrinded merged commit 66e330e into master Oct 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants