predexec
Predictive execution for LLM coding agents. predexec collapses an adaptive, multi-level tool sequence into a single model round-trip: the model pre-compiles its branch decisions into a tree of deterministic predicates, and an engine walks the tree with no model call between levels. On a request-limited free provider this trades abundant tokens for scarce provider requests.
This package ships as both a pi coding agent
extension and an opencode plugin, each registering one tool, predexec.
See How it works below for the design and current status.
Status: read-only MVP. The pure-TS core, pi adapter, and opencode adapter are done and unit-tested. predexec speculates read-only only — any write/install/delete hard-stops before running.
How it works
The model fills in a plan tree: each node runs a batch of shell commands and/or read-only
tool calls (read/grep/find/ls); each edge is a machine-evaluable condition on that
node's output. After running a node, the engine
evaluates outgoing edges in order, follows the first match to a child, and repeats — with no
model in the loop. It stops and returns a transcript when it reaches:
| stop | meaning |
|---|---|
leaf |
no edges — success path complete (the only non-fallback stop) |
noEdgeMatch |
no edge matched — benign miss, agent resumes normally |
maxDepth |
depth cap hit |
mutationStop |
next node writes/installs/deletes — hard stop before any mutation |
error |
invalid plan (returned gracefully, never thrown) |
aborted |
abort signal |
Adaptive depth. Plan as deep as you can confidently predict each branch. A tree of one node with no edges is valid and expected — that's just running a command (depth 0). Depth scales up only when branches are genuinely predictable.
Condition DSL (confidence-tiered)
HIGH-confidence (may gate deeper speculation): exitCode, fileExists, jsonPath, numeric,
always. LOW-confidence (may branch only to a read-only node): match (regex over stdout/stderr).
Install
pi coding agent
pi install npm:predexec
That's the whole install. pi fetches the package from npm, runs npm install --omit=dev
(one runtime dependency, zod), and registers the predexec tool from the package's
pi.extensions manifest (plus a terse routing skill from pi.skills,
skills/predexec/SKILL.md) — no build step (it's loaded as .ts via jiti). Once pi
starts, the model routes multi-step work through it on its own.
pi -e npm:predexec # try it for one run, no settings change
pi remove npm:predexec # uninstall
pi update --extensions # update installed packages
Verify:
pi list # must show npm:predexec and its install path
node node_modules/predexec/bin/predexec.mjs doctor # all install checks green
Then start pi and try the prompt under A prompt to see it work —
the tool result's details (pathTaken, stoppedReason) confirm the engine actually walked
a plan tree.
To install from the git repo HEAD instead of the published npm release:
pi install git:github.com/FuriousZen/predexec
Prerequisites: Node 22+ and the pi coding agent on PATH (npm i -g @earendil-works/pi-coding-agent), authenticated for some provider. The simplest way is an env
var — pi auto-detects provider keys from the environment (OPENCODE_API_KEY, NVIDIA_API_KEY,
OPENROUTER_API_KEY, …), so no ~/.pi editing is required.
opencode
Add predexec to your opencode.json (project root, or ~/.config/opencode/opencode.json for global):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["predexec"]
}
That's the whole install — opencode resolves the plugin from npm, loads .opencode/plugins/predexec.ts
in-process via Bun, and registers the predexec tool natively. No global install, no wrapper file.
Restart opencode after editing. To update, bump the version (or use "predexec@latest") and restart.
Verify (no model request needed):
node node_modules/predexec/bin/predexec.mjs doctor # static install checks
node node_modules/predexec/bin/predexec.mjs doctor --live # live probe: spawns opencode, confirms tool registered
opencode serve --port 4599 &
curl -s localhost:4599/experimental/tool/ids # must include "predexec"
If predexec is missing from the list, the plugin was silently skipped — opencode surfaces
plugin load failures only as internal session events, so this curl is the reliable check.
Then, in a session, try the prompt under A prompt to see it work.
The plugin injects a one-line routing rule into the system prompt as a guarded fallback.
To steer declaratively instead, copy the routing block into your project's AGENTS.md:
curl -fsSL https://raw.githubusercontent.com/FuriousZen/predexec/main/configs/opencode/AGENTS.md -o AGENTS.md
(A plugin install has no project node_modules — opencode keeps the package in its own
cache — so fetch the block from the repo, or cp configs/opencode/AGENTS.md from a clone.)
When opencode loads that natively, the plugin detects it (a quorum of routing-rule markers, not a mere mention of the name) and skips its own injection — no duplication.
For local development, opencode also auto-discovers .opencode/plugins/*.ts, so running opencode
inside a clone of this repo picks up .opencode/plugins/predexec.ts directly.
Prerequisites: the opencode CLI installed and authenticated for some provider.
predexec's payoff is largest on a request-limited free tier (OpenCode Zen free models, NVIDIA NIM, OpenRouter free).
Using the bundled devcontainer? Nothing to do —
post-createauto-installs predexec on every rebuild, and.devcontainer/.envinjectsNVIDIA_API_KEY+OPENCODE_API_KEYso the agent is authenticated on first boot (see.env.example).
A prompt to see it work
A read-only, structurally predictable task — predexec's sweet spot:
Detect this project's package manager and run its test script.
The model can plan one tree: probe for a lockfile / read package.json scripts, branch on
what it finds (fileExists pnpm-lock.yaml, jsonPath scripts.test exists), and run the right
test command — resolving several branch points in a single round-trip instead of one model
call per step. Inspect the tool result's details (depthReached, pathTaken,
stoppedReason, edgesEvaluated/edgesMatched) to see the path the engine walked.
Doctor & stats
predexec ships a zero-dep CLI (bin/predexec.mjs) for install diagnostics and request accounting:
npx -y predexec doctor # checklist: node version, pi/opencode config, cache, zod
npx -y predexec doctor --live # + spawns opencode and probes tool registration
npx -y predexec stats # aggregate recorded runs: ops collapsed, requests saved, edge hit-rate
Stats are append-only JSONL in $PREDEXEC_STATE_DIR (or $XDG_STATE_HOME/predexec, or
~/.local/state/predexec). Each adapter calls recordRun after every runPlanTree — fire-and-forget,
errors swallowed (a stats failure must never break a tool call).
Develop / contribute
Clone and use pnpm (the project's package manager):
git clone https://github.com/FuriousZen/predexec && cd predexec
corepack enable # makes pnpm available (ships with Node)
pnpm install
pnpm test # vitest unit tests (core)
pnpm run typecheck # tsc --noEmit
Load your working copy live in pi while iterating — no build, jiti loads the .ts:
pi -e /path/to/predexec/.pi/extension/index.ts # or just run `pi` inside the repo (package.json pi.extensions)
(In the devcontainer the repo is already at /workspaces/predexec/predexec and the adapter
loads from this path, so your edits are always what's measured.)
Layout
.pi/extension/index.ts pi adapter — JSON Schema + ctx wiring, delegates to core
.opencode/plugins/predexec.ts opencode adapter — zod schema + context wiring, delegates to core
core/ PURE TS, zero harness imports (promotable to a standalone package)
types.ts conditions.ts runner.ts engine.ts coerce.ts schema.ts index.ts
steering.ts shared steering text/marker (harness-facing; not in core/)
stats.ts request-accounting recorder (append-only JSONL; harness-facing)
bin/predexec.mjs CLI: doctor + stats (plain JS, no deps)
skills/predexec/SKILL.md declarative pi routing skill (loaded via pi.skills)
configs/opencode/AGENTS.md drop-in routing block for opencode projects