@tranhoangnguyen0310/pi-flow-external 2.2.0-external.0 → 2.3.1-external.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -7
- package/CHANGELOG.md +45 -0
- package/CONTEXT.md +7 -5
- package/README.md +49 -13
- package/docs/field-testing.md +30 -14
- package/package.json +5 -4
- package/scripts/field-report.mjs +1 -1
- package/src/core/agent-snapshot.ts +76 -0
- package/src/core/agy.ts +4 -0
- package/src/core/claude.ts +4 -0
- package/src/core/codex.ts +4 -0
- package/src/core/display.ts +3 -0
- package/src/core/grok.ts +686 -0
- package/src/core/permissions.ts +24 -1
- package/src/core/progress.ts +6 -2
- package/src/core/run-inspection.ts +41 -2
- package/src/core/run-projection.ts +233 -0
- package/src/core/run-render.ts +77 -0
- package/src/core/spawn.ts +33 -2
- package/src/core/subagent-render.ts +43 -3
- package/src/default-roles.ts +1 -1
- package/src/defaults.ts +23 -7
- package/src/external-command.ts +1 -53
- package/src/external-help.ts +14 -6
- package/src/external-runs.ts +354 -152
- package/src/pi-subagent.ts +10 -5
- package/src/profile-creator.ts +6 -6
- package/src/profiles.ts +9 -3
- package/src/prompts.ts +2 -2
- package/src/settings.ts +2 -2
- package/src/types.ts +22 -2
- package/src/workflow/runtime-values.ts +12 -1
- package/src/workflow/tool.ts +29 -53
package/AGENTS.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## pi-flow external contract
|
|
4
4
|
|
|
5
|
-
This fork changes the original pi-flow contract: `Agent` is not a generic Pi subagent launcher. It delegates only to external Claude Code, Codex CLI, and
|
|
5
|
+
This fork changes the original pi-flow contract: `Agent` is not a generic Pi subagent launcher. It delegates only to external Claude Code, Codex CLI, Antigravity, and Grok Build CLI harnesses, plus named Pi harness configurations (below) — in-process, per-model configs registered by the user, not a spawned CLI.
|
|
6
6
|
|
|
7
7
|
- Ordinary driver tools: `Agent`, read-only `external_help`, `external_runs`, and optional `workflow`. `pi_flow_profile_create` and `pi_flow_harness_create` are active only inside `/external profile create`.
|
|
8
8
|
- User operations use `/external`; `/pi-flow-profile create` is a temporary deprecated alias.
|
|
9
9
|
- Extension-owned concurrency, timeout, and global default-harness settings live in `$PI_CODING_AGENT_DIR/pi-flow-external/settings.json`. Profile backend/model/thinking metadata remains in `subagents/*.md`. Named Pi harness configurations live in the sibling `$PI_CODING_AGENT_DIR/pi-flow-external/harnesses.json` registry (one shared file, `pi-<label>` keys pinning `model`/`thinking`). Registry writes are atomic per file, but there is no inter-process lock: concurrent writers adding distinct names from separate sessions are last-writer-wins and may drop one update. This is an accepted v1 limitation shared with `settings.json` and `subagents/*.md`, not a bug to re-report. A trusted project may override `defaultHarness` via `.pi/pi-flow-external/settings.json` (that key only, read-only to the extension); explicit call harness > project default > global default. `defaultHarness` may itself name a registered `pi-*` harness.
|
|
10
|
-
- Every `Agent` call requires `description`, `prompt`, and either `role` with optional `harness` (`agy`, `claude`, `codex`, or a registered `pi-*` harness name) or the legacy exact-profile `subagent_type`. The selectors cannot be combined. Without `harness`, use the effective default harness; if that default names a `pi-*` harness that is no longer registered, the call fails with an actionable error rather than silently falling back to `agy`.
|
|
11
|
-
- Valid profiles come from `~/.pi/agent/subagents/*.md` and set `backend: claude`, `backend: codex`, `backend: agy`, or (only when also declaring `harness: <registered pi-* name>`) `backend: pi`. Use matching names such as `claude-*`, `codex-*`, `agy-*`, or `<harness>-*`; the assisted creator enforces that convention.
|
|
12
|
-
- A default roster ships with the extension and seeds once on session start: five code-oriented roles (explorer, planner, implementer, reviewer, qa) plus the generalist worker, one storage profile per CLI backend (
|
|
10
|
+
- Every `Agent` call requires `description`, `prompt`, and either `role` with optional `harness` (`agy`, `claude`, `codex`, `grok`, or a registered `pi-*` harness name) or the legacy exact-profile `subagent_type`. The selectors cannot be combined. Without `harness`, use the effective default harness; if that default names a `pi-*` harness that is no longer registered, the call fails with an actionable error rather than silently falling back to `agy`.
|
|
11
|
+
- Valid profiles come from `~/.pi/agent/subagents/*.md` and set `backend: claude`, `backend: codex`, `backend: agy`, `backend: grok`, or (only when also declaring `harness: <registered pi-* name>`) `backend: pi`. Use matching names such as `claude-*`, `codex-*`, `agy-*`, `grok-*`, or `<harness>-*`; the assisted creator enforces that convention.
|
|
12
|
+
- A default roster ships with the extension and seeds once on session start: five code-oriented roles (explorer, planner, implementer, reviewer, qa) plus the generalist worker, one storage profile per CLI backend across four backends (24 files, but six advertised roles). Seeding never overwrites existing files and respects later deletions. Roles outside the default roster are user-created via `/external profile create`. Every registered named Pi harness automatically and immediately gets the same six roles too, synthesized in-memory from the identical canonical role source — no per-harness file needed, and no on-disk seeding for pi harnesses. An installation seeded before Grok support existed migrates forward by adding only the six new `grok-*` profiles on its next seed pass, never resurrecting a deleted or customized claude/codex/agy profile.
|
|
13
13
|
- A `backend: pi` profile is external-delegation-eligible only when it also declares `harness: <name>` for a name present in the live `harnesses.json` registry; a bare `backend: pi` profile (no `harness`, or one naming an unregistered config) is filtered out and rejected exactly as before — it belongs to Pi's native subagent system and this extension never modifies it.
|
|
14
14
|
- **Named Pi harness configuration:** a `pi-<label>` entry in `harnesses.json` pinning a `provider/model` id (resolved through Pi's own model registry) and a thinking level (`off|minimal|low|medium|high|xhigh`, always persisted explicitly). It runs in-process via Pi's own SDK, not as a spawned CLI. A pi child loads no project/user extensions, skills, prompt templates, or themes — its entire tool surface is the SDK's own builtins (`read`/`bash`/`edit`/`write`, plus `grep`/`find`/`ls` where a tier's default allow-list adds them), curated but never claimed to be an OS sandbox: `danger` tier `bash` is exactly as exposed as on any external CLI. Retry is disabled per child (in-memory, call-scoped, never touching the user's real settings) to honor this extension's no-auto-retry contract, since the underlying SDK otherwise retries transient provider errors on its own. Pi children cannot resume (no persisted session) and have no enforced budget cap — both are deliberate v1 limitations, not oversights. Custom (non-canonical) roles are still authored per harness via `/external profile create`, not shared across harnesses automatically. Follow-up capability expansion (trusted extensions/MCP/skills, shared custom roles, resumable sessions, real budget controls) is tracked in issue #43.
|
|
15
15
|
- Use Pi's native subagent system for Pi-backed scout/reviewer/planner/worker/oracle work.
|
|
16
|
-
-
|
|
16
|
+
- Permission resolution takes the least restrictive of the profile tier (or default when absent) and the caller's request: `readonly < edit < danger`. Profile permissions are a floor, not an overridable default; a parent cannot handcuff a `danger` worker by requesting `readonly`. Existing backend-specific execution floors still apply. Disclosure and receipts show the resolved tier.
|
|
17
|
+
- External CLI backends use their own tools and permission mechanisms. Codex tiers map to its `--sandbox` axis. Grok tiers also map to its `--sandbox` axis (`read-only`/`workspace`/`off`), always paired with `--permission-mode bypassPermissions`; bypass only skips the interactive prompt, and the kernel sandbox remains the enforced boundary at every tier. Grok's readonly network-blocking guarantee is Linux-only (a no-op on macOS), and sandbox startup can fail closed rather than silently downgrading on some macOS hosts (for example when `/var/run/docker.sock` resolves to a symlink). Claude falls back to `--permission-mode auto` when its effective UID is 0 because Claude refuses bypass mode under root. Execution lanes (implementer, qa, worker) require shell command authority to inspect repositories and run tests; on Claude and on pi harnesses (where `edit` tier excludes `bash`), execution lanes maintain a danger floor so agents are not artificially handcuffed. Grok needs no such floor: its `edit` tier (`--sandbox workspace`) already permits shell execution, sandboxed to writes within the workspace. Antigravity (`agy`) has no granular headless permission mode — its default sandbox denies even read-only tools — so every agy run is unsandboxed (`--dangerously-skip-permissions`) and `readonly`/`edit` on agy are advisory profile-body instructions, not a boundary. Run them only in trusted repositories and state whether the task is read-only or may edit files.
|
|
18
|
+
- Grok supports `resume` via its own `--resume <sessionId>` flag and reports native cost (`total_cost_usd`) rather than an estimate. It has no budget-enforcement mechanism, so `max_budget_usd` is recorded but unenforceable, and — like codex — it is never automatically retried (agy's one infra-failure retry exception does not apply to Grok).
|
|
17
19
|
- Children receive no parent history by default. `Agent` and workflow `agent()` can opt into `context: {mode: "recent", turns: N}` (last N user turns, including the current one) or `{mode: "full"}` (available post-compaction conversation). Snapshots exclude system instructions, thinking, tool-result metadata, and pending calls; unsupported content/images and more than 1 MiB fail explicitly. Use the smallest sufficient snapshot plus a clear task, absolute paths, and read-only/edit intent. Shared context goes to the external harness and private local evidence; avoid unnecessary sensitive history. `resume` continues an existing child and cannot be combined with sharing. Workflow children select from one frozen parent snapshot, and replay fingerprints include the transferred context.
|
|
18
20
|
- Backend-native nested agents may use a different workspace. Include explicit absolute paths and required context when asking an external backend to delegate further.
|
|
19
21
|
- `Agent` and `workflow` block by default; `background: true` returns a stable handle after validation/registration. Background work is owned by the originating session, not by the launching tool call or a daemon. Blocking-call interruption cancels work; wait interruption only stops waiting. Orderly session shutdown requests cancellation with bounded cleanup. A crash or unconfirmed shutdown leaves unfinished evidence interrupted/uncertain; restart never adopts live work.
|
|
@@ -36,7 +38,8 @@ This fork changes the original pi-flow contract: `Agent` is not a generic Pi sub
|
|
|
36
38
|
- Settings retention automatically prunes eligible completed records beyond `maxRunRecords`; active, interrupted, incomplete, and damaged records are not eligible.
|
|
37
39
|
- Structured nested-agent activity may extend the wall-clock deadline once, by one fresh base timeout, capped at twice the original deadline.
|
|
38
40
|
- Do not automatically retry failed or aborted external runs. Preserve the receipt and retry only when the user asks. Exception: the agy backend retries once on infrastructure-classified failures (auth, eligibility, network); the retry is disclosed in the receipt details (`retries`, `retryOf`) and never applies to agent-level failures, aborts, or timeouts.
|
|
39
|
-
- `external_runs` is session/project scoped. `list` pages runs and workflow roots, each carrying a shared timing projection (`queuedAt`/`executionStartedAt`/`processStartedAt`/activity timestamps plus derived `queueDelayMs`/`elapsedMs`/`activityAgeMs`/`processDurationMs`); `activityAgeMs` and a running `elapsedMs` are computed only for a target independently confirmed live by the registry, never inferred from a durable record's status field alone (an unowned "running"-looking record may be an orphaned/crashed run, not a live one). `inspect` pages `summary`/`output`/`diagnostics`/`final` for one `runId`; `final` returns only the same canonical terminal result every other view already reads (no separate backend parser, no narration promoted to final) and is empty with `finalAvailable: false` until a verified successful terminal boundary exists — including for workflows, where an intentional JSON `null` result still counts as available. `inspect` also accepts a bounded batch `runIds` (up to 20, deduplicated, order preserved, summary-only, mutually exclusive with `runId`): every requested ID's ownership is validated before any page returns, entries share one consistent nested shape regardless of live/durable/agent/workflow origin (a workflow entry omits its unbounded children array in favor of `outputRef`/`diagnosticsRef`), reuses the existing per-inspect `limitBytes` cap with no second budget, and fails with an actionable error naming the run rather than ever silently dropping a target or serving a page over the caller's own byte budget. `wait` returns outcomes only for the selected one/any/all target set and never cancels pending work; `cancel` targets one stable ID and preserves its reason. An unresolvable `wf_...` ID fails as unknown/unavailable across `inspect`/`cancel`/`wait`/batch `inspect`, never falling through to the agent-only durable reader. No routine run event sends a parent message or notification.
|
|
41
|
+
- `external_runs` is session/project scoped. `list` pages runs and workflow roots, each carrying a shared timing projection (`queuedAt`/`executionStartedAt`/`processStartedAt`/activity timestamps plus derived `queueDelayMs`/`elapsedMs`/`activityAgeMs`/`processDurationMs`); `activityAgeMs` and a running `elapsedMs` are computed only for a target independently confirmed live by the registry, never inferred from a durable record's status field alone (an unowned "running"-looking record may be an orphaned/crashed run, not a live one). `inspect` pages `summary`/`output`/`diagnostics`/`final` for one `runId`; `final` returns only the same canonical terminal result every other view already reads (no separate backend parser, no narration promoted to final) and is empty with `finalAvailable: false` until a verified successful terminal boundary exists — including for workflows, where an intentional JSON `null` result still counts as available. `inspect` also accepts a bounded batch `runIds` (up to 20, deduplicated, order preserved, summary-only, mutually exclusive with `runId`): every requested ID's ownership is validated before any page returns, entries share one consistent nested shape regardless of live/durable/agent/workflow origin (a workflow entry omits its unbounded children array in favor of `outputRef`/`diagnosticsRef`), reuses the existing per-inspect `limitBytes` cap with no second budget, and fails with an actionable error naming the run rather than ever silently dropping a target or serving a page over the caller's own byte budget. `wait` returns outcomes only for the selected one/any/all target set and never cancels pending work; while waiting it reports bounded live progress (watched targets, completed/pending counts, recent activity) through the tool's own update channel on a fixed heartbeat, stopped on settlement/error/interruption, that never mutates run state or affects cancellation; each settled outcome's `result` is spent from one shared byte budget (`limitBytes`, default 32768) across the whole response in the caller's requested order, never settlement race order — complete when it fits, `resultTruncated: true` plus the existing `outputRef`/`diagnosticsRef` when it does not, never a fixed-length teaser regardless of size; a target's evidence is never read from disk once that shared budget is already exhausted. `cancel` targets one stable ID and preserves its reason. An unresolvable `wf_...` ID fails as unknown/unavailable across `inspect`/`cancel`/`wait`/batch `inspect`, never falling through to the agent-only durable reader. No routine run event sends a parent message or notification.
|
|
42
|
+
- `list`, `inspect`, and `wait` share one run projection (`src/core/run-projection.ts`: `projectLiveAgent`/`projectDurableAgent` for agents, `projectWorkflowRun` for workflows) instead of independent per-surface field extraction. Task identity includes the resolved profile and an explicit `harness` (registered pi-* config, or equal to backend for agy/claude/codex), persisted at the source (run-record metadata, live progress nodes, workflow child snapshots) by every backend — never reparsed or guessed from a profile/subagentType name.
|
|
40
43
|
|
|
41
44
|
## Workflow contract
|
|
42
45
|
|
|
@@ -52,7 +55,7 @@ Use workflows for requested fan-out or multi-agent orchestration across Claude/C
|
|
|
52
55
|
- Test public contracts, trust/security boundaries, process lifecycle, cancellation/timeouts, profile rollback, workflow execution/resume, and receipt integrity.
|
|
53
56
|
- Do not test cosmetic rendering variations, prompt prose fragments, trivial accessors, or a model's interpretation of instructions.
|
|
54
57
|
- Do not repeat the same behavior at unit, integration, and E2E levels. A regression test must replace or extend overlapping coverage.
|
|
55
|
-
- `npm test` must remain deterministic and offline. Real-provider E2E is opt-in and change-triggered
|
|
58
|
+
- `npm test` must remain deterministic and offline. Real-provider E2E is opt-in and change-triggered. Its default lane never prompts a root LLM to pick the tool call — it builds an in-process Pi SDK session with a faux, never-streamed root model and calls the `Agent`/`workflow`/`external_runs` tool executors directly, so only the selected external backend's own child (a real spawned CLI process, or, for `pi`, a real in-process nested Pi child) is real: `npm run e2e -- --backend <claude|codex|agy|grok> [--workflow|--interrupt]`, or `npm run e2e -- --backend pi --harness <registered-name> [--workflow|--interrupt]` for a named Pi harness the caller has already registered in their own real `harnesses.json` with real credentials configured. A separate `--routing-smoke` lane spawns a real `pi` CLI process with a real root model and a plain-language instruction to exercise natural-language tool-call routing end to end; it requires real root Pi auth/config and is never the default.
|
|
56
59
|
|
|
57
60
|
## Verification and release
|
|
58
61
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,51 @@ All notable changes to pi-flow external are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [2.3.1-external.0] - 2026-09-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Resolve permission requests against the profile's minimum authority (`readonly < edit < danger`) across all harnesses. Parent requests can increase permissions but cannot reduce a worker's calibrated permission floor. Agent, workflow, and disclosures share the same resolver; backend-specific floors remain in effect.
|
|
11
|
+
|
|
12
|
+
## [2.3.0-external.0] - 2026-09-21
|
|
13
|
+
|
|
14
|
+
Unifies the run experience across direct `Agent`, `workflow`, and `external_runs` per issue #52: a shared projection, shared rendering, and a corrected model/human contract, rather than isolated renderer patches.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `Agent`, `workflow`, and `external_runs` now share one normalized run projection (`src/core/run-projection.ts`) for identity/state/timing/output, covering both agents (`projectLiveAgent`/`projectDurableAgent`) and workflows (`projectWorkflowRun`). Every `external_runs` surface — `list`, single `inspect`, batch `inspect`, and `wait` — routes through it instead of independently hand-rolling its own shape; a durable `list`/`inspect` row now also carries the same nested `task`/`state`/`output` shape a live one already had, alongside its existing flat fields for compatibility.
|
|
19
|
+
- Run identity now includes the resolved profile and harness end-to-end: `Agent`, workflow children, and every backend (`claude`/`codex`/`agy`/pi) persist an explicit `harness` (registered pi-* config, or equal to backend for external CLIs) on `SubagentProgressNode`/`SubagentToolDetails`/`WorkflowAgentSnapshot` and in run-record metadata — never reparsed or guessed from a profile/subagentType name. `RunTaskProjection.profile`/`.harness` are populated from this real persisted data (`src/profiles.ts`'s `selectorHarness` is the one shared resolution).
|
|
20
|
+
- `external_runs` registers its own `renderCall`/`renderResult` for the first time, covering `list`, single/batch `inspect`, `wait`, and `cancel` with the same reusable header/row presentation `Agent`/`workflow` already use (`src/core/run-render.ts`, shared with `/external runs`' own text formatting).
|
|
21
|
+
- `external_runs wait` reports bounded live progress through its update channel while waiting — watched targets, completed/pending counts, and recent activity — on a fixed heartbeat independent of any single target settling; it stops on settlement, error, or interruption without ever cancelling watched work.
|
|
22
|
+
- Text answers now render as Markdown where a host theme is available (`renderOutputText` in `src/core/subagent-render.ts`, shared by `Agent`'s expanded output, `workflow`'s final text-answer output, and `external_runs`'s `output`/`final` views), falling back to plain text — never throwing — when no theme has been initialized (headless/RPC callers, tests). A workflow's structured (non-string) result stays formatted data, never Markdown-interpreted, per this issue's "no invented prose answer" contract.
|
|
23
|
+
- Workflow `agent()` accepts `description` as a compatible alias for `label`, matching direct `Agent`'s common task-name option; setting both to different values is rejected.
|
|
24
|
+
- `WorkflowToolDetails` gained an additive `lifecycleStatus` field normalizing the legacy internal `"completed"` state word to the same vocabulary `Agent`'s `details.status` already uses (`"done"`), for a coordinating model reading either tool's details. The internal `status` field and its literal `"completed"` value are unchanged for existing readers (journal replay, rendering internals).
|
|
25
|
+
- Documented ccstyle (`pi-cc-extensions`) host-renderer integration: the `excludeRenderers` setting is the supported way to preserve `Agent`/`workflow`/`external_runs`'s own rendering under that host (README "Host renderer integration").
|
|
26
|
+
- Added an authoritative cross-surface contract test (`test/run-contract.test.ts`) asserting the same run reports the same identity/status/output facts across `list`, single `inspect`, batch `inspect`, and `wait`, and that a workflow child's identity matches what `external_runs` independently reports for that same child runId.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `external_runs wait` no longer clips every settled outcome to a fixed 512-character `preview`. Each response now spends one shared byte budget (`limitBytes`, default 32768) across all settled outcomes in the caller's REQUESTED order (never settlement race order — two calls with the same targets and final states now spend the budget identically regardless of which target happened to settle first): a result that fits is returned complete under the new `result` field; one that does not is truncated to what remains with `resultTruncated: true`, alongside the existing `outputRef`/`diagnosticsRef` to read the rest. A disk read of a target's evidence is now skipped entirely once the shared budget is already exhausted, rather than reading and then discarding it. The old `preview` field is removed.
|
|
31
|
+
- `liveSummary`/`journalSummary`/batch `resolveRunSummaryEntry`'s workflow branch now build their `task`/`state`/`output` through the single shared `projectWorkflowRun`, fixing a real drift: batch inspect previously omitted `output.status` entirely (present on the other two), and `liveSummary` never stated a top-level `live` flag.
|
|
32
|
+
- `WorkflowAgentSnapshot` field copying (`workflow/tool.ts`) now goes through shared helpers (`src/core/agent-snapshot.ts`) instead of an inline per-field copy. Fixes a real disclosure/evidence gap: a workflow child's `retries`, `retryOf`, and `permissionRequested` were never copied onto its snapshot at all (silently dropped the moment a retried/elevated child ran inside a workflow instead of as a direct `Agent` call), and `thinkingClamped` was dropped on the running-progress copy.
|
|
33
|
+
- Corrected the workflow help's background example: `background` is a `workflow` tool call parameter, never a `meta` field. The previous example's `meta.background: true` was silently ignored and did not run the workflow in the background.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- `preview`'s hidden double-truncation: a full in-memory `outcome.result` under 512 characters was fine, but the exact bug reported in issue #52 (`external_runs wait` returning `kind: "agent"` with a clipped result) is now gone for any result that fits the shared budget.
|
|
38
|
+
Adds `grok` as a fourth external CLI backend, delegating to the official Grok Build CLI alongside Claude Code, Codex CLI, and Antigravity. Investigation notes: `docs/plans/grok-backend-prep.md`; implementation plan: `docs/plans/2026-05-21-grok-cli-implementation.md`; ex-ante design issue: [#51](https://github.com/tranhoangnguyen03/pi-flow-external/issues/51).
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `grok` backend (`src/core/grok.ts`), verified against Grok Build CLI `1.0.40`, installed and authenticated independently of Pi. Normal runs use `--output-format streaming-messages-json` (a Claude-compatible `assistant`/`result` event envelope); structured output uses `--json-schema` and parses the single terminal document's `structuredOutput` field. Prompts are passed through a temporary `--prompt-file` since Grok does not read stdin. Terminal success requires an observed `result` event affirming `subtype: "success"`, `is_error: false`, and `stop_reason: "end_turn"` — never inferred from exit code or accumulated text alone.
|
|
43
|
+
- Native kernel-sandbox permission tiers: `readonly` (`--sandbox read-only`), `edit` (`--sandbox workspace`), and `danger` (`--sandbox off`), always paired with `--permission-mode bypassPermissions` — bypass only skips the interactive approval prompt; the sandbox remains the real enforced boundary at every tier. `edit` already permits shell execution, so Grok execution-lane roles need no `edit`→`danger` floor. `readonly`'s network-blocking guarantee is Linux-only, and sandbox startup can fail closed on macOS hosts (for example, a symlinked `/var/run/docker.sock`) rather than silently running unsandboxed.
|
|
44
|
+
- `resume` support via Grok's own `--resume <sessionId>` and native cost reporting (`total_cost_usd`); Grok has no budget-enforcement mechanism, so `max_budget_usd` is recorded but unenforceable, and — like codex — failed or aborted runs are never automatically retried (agy's one infra-failure retry exception does not apply to Grok).
|
|
45
|
+
- Default roster migration: a pre-existing installation gains only the six new `grok-*` profiles on its next seed pass (24 default profiles total across four backends), without resurrecting any profile the user previously deleted or customized; a fresh installation seeds all 24 directly.
|
|
46
|
+
- Offline coverage in `test/grok-backend.test.ts` plus extended permission/defaults/spawn/profile/run-inspection regression tests; opt-in real-provider coverage via `npm run e2e -- --backend grok` and `--backend grok --workflow`.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Overhauled `scripts/e2e/external.mjs`: the default `npm run e2e -- --backend <...> [--workflow|--interrupt]` lane no longer routes tool selection through a real, paid, nondeterministic root Pi LLM. It now builds an in-process Pi SDK session with a faux, never-prompted root model (via the same public SDK/faux-provider patterns `test/helpers/pi-subagent-harness.ts` already uses) and calls the `Agent`/`workflow`/`external_runs` tool executors directly — only the selected external backend's own child (a real spawned CLI process, or, for `--backend pi`, a real in-process nested Pi child) is real. This also fixes two related failures the old design could hit: concurrent direct/workflow runs colliding on a `Date.now()`-only temporary role name (now `randomUUID()`), and an isolated agent directory hiding a real custom root model/auth and surfacing as an unrelated coordinator startup failure (the new default lane never needs a real root model at all). The prior natural-language, real-root-LLM routing check survives unchanged as an explicit, opt-in `--routing-smoke` flag for when role discovery, tool descriptions, or coordinator guidance changes.
|
|
51
|
+
|
|
7
52
|
## [2.2.0-external.0] - 2026-09-20
|
|
8
53
|
|
|
9
54
|
### Added
|
package/CONTEXT.md
CHANGED
|
@@ -4,7 +4,7 @@ This package is a fork of pi-flow whose `Agent` and `workflow` tools are reserve
|
|
|
4
4
|
|
|
5
5
|
## Domain language
|
|
6
6
|
|
|
7
|
-
- **External profile:** A markdown file under `~/.pi/agent/subagents/*.md` whose filename and frontmatter select `backend: claude`, `backend: codex`, `backend: agy`, or (only alongside a registered `harness:`) `backend: pi`.
|
|
7
|
+
- **External profile:** A markdown file under `~/.pi/agent/subagents/*.md` whose filename and frontmatter select `backend: claude`, `backend: codex`, `backend: agy`, `backend: grok`, or (only alongside a registered `harness:`) `backend: pi`.
|
|
8
8
|
- **Native Pi subagent:** A subagent exposed by Pi's native subagent system. This fork intentionally does not route it through `Agent`.
|
|
9
9
|
- **Agent call:** A direct external delegation selected by `role` and optional `harness` (an external CLI or a registered named Pi harness), or by legacy exact-profile `subagent_type`.
|
|
10
10
|
- **Named Pi harness configuration:** A `pi-<label>` entry in `harnesses.json` pinning a `provider/model` id and a thinking level, run in-process via Pi's own SDK rather than as a spawned CLI. `harness` selects *which* configuration; `backend` (always the literal `"pi"` for these) selects the execution mechanism. The six default roles are synthesized automatically for every registered pi harness from the same canonical source the three CLI backends' default files already come from.
|
|
@@ -24,7 +24,7 @@ This package is a fork of pi-flow whose `Agent` and `workflow` tools are reserve
|
|
|
24
24
|
## Routing rule
|
|
25
25
|
|
|
26
26
|
- Native Pi work -> native subagent tool.
|
|
27
|
-
- Claude Code / Codex CLI / Antigravity work, and named Pi harness work -> this extension's `Agent` or `workflow`.
|
|
27
|
+
- Claude Code / Codex CLI / Antigravity / Grok Build CLI work, and named Pi harness work -> this extension's `Agent` or `workflow`.
|
|
28
28
|
|
|
29
29
|
The split is global and intentional to avoid tool ambiguity across projects. The ordinary driver sees `Agent`, optional `workflow`, and read-only `external_help`; the profile finalizer is activated only by `/external profile create`. External children start in the requested working directory, but backend-native nested helpers may create or use a separate workspace; prompts that request further nesting should include explicit absolute paths and all required context. Named Pi harness children are the one exception to "backend-native nested helpers may use a different workspace": a pi child runs in-process with no extensions, skills, prompts, or themes loaded, so it structurally cannot itself launch a further nested agent.
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ The split is global and intentional to avoid tool ambiguity across projects. The
|
|
|
32
32
|
|
|
33
33
|
User operations are namespaced under `/external`: status, Doctor, settings, profiles, profile creation, workflows, interactive run navigation, durable run summary/pruning, and help. `/external runs` uses the same registry/readers as `external_runs`, including every cursor-paged workflow child and output/diagnostic page. Extension-owned concurrency, timeout, and global default-harness settings live in `$PI_CODING_AGENT_DIR/pi-flow-external/settings.json`; profiles remain authoritative for downstream backend/model/thinking metadata. Named Pi harness configurations live in the sibling `pi-flow-external/harnesses.json` registry. A trusted project may override only `defaultHarness`, which may itself name a registered `pi-*` harness.
|
|
34
34
|
|
|
35
|
-
> Next agent: full implementation map is `docs/ARCHITECTURE_SNAPSHOT.md` (verified 2026-09-
|
|
35
|
+
> Next agent: full implementation map is `docs/ARCHITECTURE_SNAPSHOT.md` (verified 2026-09-21). Keep this file and `AGENTS.md` as the persistent breadcrumbs.
|
|
36
36
|
|
|
37
37
|
## Design stance
|
|
38
38
|
|
|
@@ -48,10 +48,12 @@ Applied to Antigravity (`agy`), that stance is absolute: the harness offers no g
|
|
|
48
48
|
|
|
49
49
|
Applied to named Pi harness configurations, the same "get out of the way" stance takes a different, honestly-scoped shape: a curated, builtins-only tool surface (no project/user extensions, skills, prompts, or themes) genuinely bounds which tool *names* a pi child can reach — that claim is mechanically true because nothing else loads — but it is not a claim that `bash` at `danger` tier is any less dangerous than on an external CLI once it is granted. `readonly`/`edit` tiers get a real default tool allow-list (`read`/`grep`/`find`/`ls`, plus `edit`/`write` at `edit`) so those tiers are actually usable, not just technically restrictive. Execution-lane roles requested at `edit` are still elevated to `danger` (the same floor as Claude), because `edit` excludes `bash` entirely and an execution lane without shell access cannot do its job.
|
|
50
50
|
|
|
51
|
+
Applied to the Grok Build CLI, permission tiers map onto its own kernel-enforced `--sandbox` axis (`read-only`/`workspace`/`off`), always paired with `--permission-mode bypassPermissions` — bypass only removes the interactive approval prompt, never the sandbox itself, so every tier is a genuine boundary rather than an advisory one. `edit` already permits shell execution (sandboxed to workspace writes), so, unlike Claude and pi, Grok execution-lane roles need no `edit`→`danger` floor. `readonly`'s network-blocking guarantee is Linux-only, and sandbox startup can fail closed on some macOS hosts rather than silently running unsandboxed; that fail-closed behavior is preserved rather than weakened for cross-platform convenience.
|
|
52
|
+
|
|
51
53
|
## Known inelegance
|
|
52
54
|
|
|
53
|
-
<!-- ponytail: one-backend-per-file profile format forces role x backend file duplication; extend src/profiles.ts with multi-backend profiles (e.g. backends: [claude, codex, agy] plus per-backend model map) if maintaining N copies of identical role bodies ever hurts -->
|
|
54
|
-
A standardized role roster needs one file per role per backend (e.g. `claude-qa`, `codex-qa`, `agy-qa` with identical bodies) because the profile format binds `backend:` and `model:` to a single file. The duplication is accepted for now; a future format extension could let one role file cover all backends. Named Pi harnesses partially resolve this for the six canonical roles specifically (synthesized once from a single shared source, applied identically to every registered `pi-*` config, with zero files written) — but a *custom* role outside the six still needs one file per pi harness, exactly like the
|
|
55
|
+
<!-- ponytail: one-backend-per-file profile format forces role x backend file duplication; extend src/profiles.ts with multi-backend profiles (e.g. backends: [claude, codex, agy, grok] plus per-backend model map) if maintaining N copies of identical role bodies ever hurts -->
|
|
56
|
+
A standardized role roster needs one file per role per backend (e.g. `claude-qa`, `codex-qa`, `agy-qa`, `grok-qa` with identical bodies) because the profile format binds `backend:` and `model:` to a single file. The duplication is accepted for now; a future format extension could let one role file cover all backends. Named Pi harnesses partially resolve this for the six canonical roles specifically (synthesized once from a single shared source, applied identically to every registered `pi-*` config, with zero files written) — but a *custom* role outside the six still needs one file per pi harness, exactly like the four CLI backends; a user with several registered pi harnesses wanting the same custom role on all of them still duplicates one file per harness. The same applies to `permission:` tiers: one tier per profile, but backends interpret tiers differently (Claude and pi deny Bash at anything below `danger`), so command-running roles declare `danger` and maintain `danger` as their floor even if an explicit `edit` override is requested. Custom profiles outside the role-name convention get that floor only by declaring `permission: danger`; an undeclared custom profile that meets an `edit` tier still hits headless Bash denials (or, on pi, a curated tool set without `bash`), so `permission: danger` doubles as the lane's "needs shell" capability declaration. Named Pi harnesses also ship with no resume and no enforced budget cap in v1 — both deliberate limitations, tracked alongside the shared-custom-role gap in issue #43, not oversights.
|
|
55
57
|
|
|
56
58
|
## Evidence boundary
|
|
57
59
|
|
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ External agent delegation for [pi](https://github.com/earendil-works/pi) through
|
|
|
5
5
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) profiles with `backend: claude`
|
|
6
6
|
- [Codex CLI](https://github.com/openai/codex) profiles with `backend: codex`
|
|
7
7
|
- Antigravity profiles with `backend: agy`
|
|
8
|
+
- [Grok Build CLI](https://github.com/xai-org/grok-build) profiles with `backend: grok`
|
|
8
9
|
- Named Pi harness configurations (`pi-<label>`) — in-process, per-model configs you register yourself, not a spawned CLI
|
|
9
10
|
|
|
10
11
|
The ordinary driver has four tools (`workflow` can be disabled):
|
|
@@ -14,7 +15,7 @@ The ordinary driver has four tools (`workflow` can be disabled):
|
|
|
14
15
|
- `external_help` returns role details, permission behavior, or workflow guidance on demand.
|
|
15
16
|
- `external_runs` lists, inspects, waits for, and cancels session-owned runs.
|
|
16
17
|
|
|
17
|
-
`Agent` and `workflow` accept external roles with an optional harness override — one of the
|
|
18
|
+
`Agent` and `workflow` accept external roles with an optional harness override — one of the four CLIs, or a registered named Pi harness. Use pi's native subagent system for Pi-backed agents. The `pi_flow_profile_create` and `pi_flow_harness_create` finalizers are active only during `/external profile create`.
|
|
18
19
|
|
|
19
20
|
## Install
|
|
20
21
|
|
|
@@ -48,18 +49,20 @@ Install and authenticate each external CLI you intend to use:
|
|
|
48
49
|
claude --version
|
|
49
50
|
codex --version
|
|
50
51
|
agy --version
|
|
52
|
+
grok --version
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
Pi's coordinator model and the external CLIs authenticate independently. A working Claude, Codex, or
|
|
55
|
+
Pi's coordinator model and the external CLIs authenticate independently. A working Claude, Codex, Antigravity, or Grok login does not authenticate the root Pi model. The Grok Build CLI installs and authenticates entirely separately from Pi: install with `curl -fsSL https://x.ai/cli/install.sh | bash`, then authenticate with `grok login` or an `XAI_API_KEY` environment variable. This integration is verified against Grok Build CLI `1.0.40`.
|
|
54
56
|
|
|
55
57
|
External agents use the effective permission tier and each harness's native mechanism:
|
|
56
58
|
|
|
57
59
|
- Claude: `--permission-mode plan`, `--permission-mode acceptEdits`, or `--dangerously-skip-permissions`
|
|
58
60
|
- Codex: `--sandbox read-only`, `workspace-write`, or `danger-full-access`
|
|
59
61
|
- Antigravity: always `--dangerously-skip-permissions`
|
|
62
|
+
- Grok: `--sandbox read-only`, `workspace`, or `off`, always alongside `--permission-mode bypassPermissions` — bypass only skips the interactive approval prompt; the kernel sandbox remains the enforced boundary. `readonly`'s network-blocking guarantee is Linux-only (a no-op on macOS), and sandbox startup can fail closed on some macOS hosts (for example when `/var/run/docker.sock` resolves to a symlink) rather than silently running unsandboxed.
|
|
60
63
|
- Named Pi harnesses: a curated `tools:` allow-list (`read`/`grep`/`find`/`ls` at `readonly`; those plus `edit`/`write` at `edit`), or the SDK's own default active tools (`read`/`bash`/`edit`/`write`) at `danger`
|
|
61
64
|
|
|
62
|
-
Claude refuses bypass mode when its effective UID is `0`; in that case the extension uses `--permission-mode auto`. External execution lanes (like `implementer`, `qa`, and `worker`) require shell execution to inspect repositories, run tests, and verify code. On Claude Code and named Pi harnesses, headless/`edit`-tier access excludes shell entirely; therefore, execution lanes maintain a `danger` floor so the model is not artificially handcuffed by permission blocks. Run external agents only in repositories you trust and state whether each task is read-only or may edit files.
|
|
65
|
+
Claude refuses bypass mode when its effective UID is `0`; in that case the extension uses `--permission-mode auto`. External execution lanes (like `implementer`, `qa`, and `worker`) require shell execution to inspect repositories, run tests, and verify code. On Claude Code and named Pi harnesses, headless/`edit`-tier access excludes shell entirely; therefore, execution lanes maintain a `danger` floor so the model is not artificially handcuffed by permission blocks. Grok needs no such floor: its `edit` tier (`--sandbox workspace`) already permits shell execution, sandboxed to writes within the workspace. Run external agents only in repositories you trust and state whether each task is read-only or may edit files.
|
|
63
66
|
|
|
64
67
|
The TUI labels a direct run with its effective access, including `unsandboxed external CLI` for danger and all Agy runs, `Pi SDK child · host access · curated tools` for a danger-tier named Pi harness, and shows `external host access` while workflow work is active. These labels disclose actual execution authority; they do not turn a read-only prompt into an enforced permission boundary.
|
|
65
68
|
|
|
@@ -113,7 +116,7 @@ Profiles live in:
|
|
|
113
116
|
~/.pi/agent/subagents/<name>.md
|
|
114
117
|
```
|
|
115
118
|
|
|
116
|
-
Names may contain lowercase letters, numbers, and hyphens. A profile must declare `backend: claude`, `backend: codex`, `backend: agy`, or (alongside `harness: <a registered pi-* name>`) `backend: pi`, and its name should start with the matching harness name.
|
|
119
|
+
Names may contain lowercase letters, numbers, and hyphens. A profile must declare `backend: claude`, `backend: codex`, `backend: agy`, `backend: grok`, or (alongside `harness: <a registered pi-* name>`) `backend: pi`, and its name should start with the matching harness name.
|
|
117
120
|
|
|
118
121
|
Example Claude profile, `~/.pi/agent/subagents/claude-explorer.md`:
|
|
119
122
|
|
|
@@ -140,11 +143,16 @@ backend: agy
|
|
|
140
143
|
model: gemini-3.7-flash-high
|
|
141
144
|
```
|
|
142
145
|
|
|
146
|
+
```yaml
|
|
147
|
+
backend: grok
|
|
148
|
+
model: grok-4.6
|
|
149
|
+
```
|
|
150
|
+
|
|
143
151
|
Profile instructions become the external agent's system instructions. A profile's `description` is also shown as the user-visible reason for its selection, so keep it concise and concrete. External CLIs use their own tools, so a profile's `tools:` field does not control them; a pi profile's `tools:` field does apply, intersected with its permission tier's curated tool table. A `backend: pi` profile is only available to this extension when it also declares `harness: <name>` for a name registered in `harnesses.json` (see below); a bare `backend: pi` profile, or no backend at all, belongs to Pi's native subagent system and is never modified by this extension.
|
|
144
152
|
|
|
145
153
|
### Default profiles
|
|
146
154
|
|
|
147
|
-
On first session start the extension seeds a default roster — five code-oriented roles (explorer, planner, implementer, reviewer, qa) plus the generalist worker — as one storage profile per backend (
|
|
155
|
+
On first session start the extension seeds a default roster — five code-oriented roles (explorer, planner, implementer, reviewer, qa) plus the generalist worker — as one storage profile per backend across four backends (24 files). The compact agent catalog advertises each role once rather than presenting 24 choices. Seeding happens once: it never overwrites existing files, and profiles you delete or customize afterwards stay that way. Default profiles leave `model` and `thinking` unpinned so they track the CLI's own model and the current Pi thinking level. Roles not in the default roster can be added with `/external profile create`. An installation seeded before Grok support existed gains only the six new `grok-*` profiles the next time it seeds, without resurrecting any claude/codex/agy profile you previously deleted or customized.
|
|
148
156
|
|
|
149
157
|
Project-local profiles are not supported; global profiles are used for both global and project-only package installations.
|
|
150
158
|
|
|
@@ -176,7 +184,7 @@ Agent({
|
|
|
176
184
|
});
|
|
177
185
|
```
|
|
178
186
|
|
|
179
|
-
A custom (non-canonical) role still needs its own profile file per harness, the same as for the
|
|
187
|
+
A custom (non-canonical) role still needs its own profile file per harness, the same as for the four CLI backends: `~/.pi/agent/subagents/pi-deepseek-security-reviewer.md` with `backend: pi` and `harness: pi-deepseek`. That file's body/permission/tools may be customized; its `model`/`thinking` are not — they always come from the harness's registered config, and a file that tries to override them to a different value is rejected rather than silently honored.
|
|
180
188
|
|
|
181
189
|
**v1 scope, by design:** a pi child's entire tool surface is the SDK's own builtins (`read`/`bash`/`edit`/`write`, plus `grep`/`find`/`ls` at `readonly`/`edit` tiers) — no project/user extensions, skills, prompt templates, or themes load into it. This bounds which tool *names* exist; it does not make `bash` at `danger` tier any less exposed than on an external CLI. Retry is disabled per pi child (in-memory, never touching your real Pi settings) so a transient provider error fails immediately rather than silently retrying. Pi children cannot resume a prior conversation and have no enforced budget cap. Expanding this capability set (trusted extensions/MCP/skills, a shared custom-role format, resumable sessions, real budget controls) is tracked in [issue #43](https://github.com/tranhoangnguyen03/pi-flow-external/issues/43).
|
|
182
190
|
|
|
@@ -251,6 +259,24 @@ Completed rows show a short evidence identifier and result preview. Press **Ctrl
|
|
|
251
259
|
|
|
252
260
|
Workflows show access once at the workflow level, retain done/active/queued/failed counts, and expose bounded final output plus the workflow journal when expanded. Hidden child rows remain reachable through `/external runs`. Active rows report last-activity freshness independently of the spinner. Raw backend events remain in local records rather than flooding the default terminal view.
|
|
253
261
|
|
|
262
|
+
`Agent`, `workflow`, and `external_runs` all register their own `renderCall`/`renderResult` — the same reusable header, status/timing/output presentation, and disclosure rules across all three, so a workflow's card is more hierarchy over the same visual language rather than a different one, and `external_runs` (list/inspect/wait) reads like the tools it observes rather than raw JSON. A text answer's final output — `Agent`'s own expanded result, a workflow's string result, and `external_runs`' `output`/`final` views — renders as Markdown where a host theme is available, falling back to plain text (never a crash) otherwise; a workflow's structured (object/array) result stays formatted data rather than Markdown-interpreted prose.
|
|
263
|
+
|
|
264
|
+
### Host renderer integration
|
|
265
|
+
|
|
266
|
+
Stock Pi renders every tool's own `renderCall`/`renderResult` directly, so the cards above work with no configuration.
|
|
267
|
+
|
|
268
|
+
**Compatibility caveat — pi-cc / ccstyle:** our live progress renderers are not compatible with the default rendering overrides in [`pi-cc-extensions`](https://www.npmjs.com/package/pi-cc-extensions). Manual testing showed workflow and wait progress replaced by a generic `Pending…` display while Agent retained its dedicated renderer. Expanded generic output can also duplicate raw JSON. This is a display limitation, not evidence that the underlying run has stopped.
|
|
269
|
+
|
|
270
|
+
To preserve this extension's renderers, add its three tool names to pi-cc's `excludeRenderers` setting in `~/.pi/agent/pi-cc-extensions.json`. Merge these entries into your existing configuration; do not replace other settings or exclusions:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"excludeRenderers": ["Agent", "workflow", "external_runs"]
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Restart Pi after changing the configuration. Exclusions use **tool names**, not the extension's package name. All three tools above provide custom renderers. This is pi-cc's supported renderer-preservation mechanism; do not edit its installed package files. End-to-end progress rendering with these exclusions still needs confirmation in your installed pi-cc version. If it continues to show only `Pending…`, disable pi-cc when you need our live progress display. Full compatibility with pi-cc's default overrides is not claimed.
|
|
279
|
+
|
|
254
280
|
## Background runs and supervision
|
|
255
281
|
|
|
256
282
|
`Agent` and `workflow` are blocking by default — an ordinary call waits for its child and returns the result, run everything this way unless the parent has other work to do first. Add `background: true` only when the parent can productively proceed before completion; it returns a stable `run_...` or `wf_...` handle after validation and registration while the originating Pi session continues to own the work:
|
|
@@ -269,7 +295,7 @@ Use `external_runs` with these actions:
|
|
|
269
295
|
- `list`: current session/project runs; use `cursor` for run pages and `workflowCursor` for workflow pages. `workflowRunId` filters children of one workflow. Rows carry a `timing` projection (`queueDelayMs`, `elapsedMs`, `activityAgeMs` when live, `processDurationMs`) plus `outputAvailable`/`finalAvailable`.
|
|
270
296
|
- `inspect`: single `runId` with `view: "summary" | "output" | "diagnostics" | "final"`, and optional opaque `cursor`/`limitBytes` (max 64 KiB, same cap for every view). Follow `nextCursor` to avoid truncation. `summary` includes the same `timing` projection as `list`, plus `output.finalAvailable`. `final` returns only the verified canonical terminal answer — empty with `finalAvailable: false` until a successful terminal boundary exists; it never promotes partial/narration text. `output` stays the combined stream (assistant messages plus canonical result) and is unchanged.
|
|
271
297
|
- `inspect` with `runIds` instead of `runId` (up to 20, deduplicated, order preserved): a single bounded batch of `summary`-only projections — one cheap request to see whether several selected background children are queued, running, or terminal, each with `outputRef`/`diagnosticsRef` for follow-up detail. Ownership of every requested ID is validated before any page is returned. Reuses the same `limitBytes` cap as single-run inspection; pages contain whole target entries and continue through `nextCursor`, without invalidation from ordinary live progress. If one compact entry cannot fit, an actionable error asks you to increase `limitBytes` or inspect that run individually; no target is silently dropped. `runId` and `runIds` are mutually exclusive, and batch `view` must stay `summary`.
|
|
272
|
-
- `wait`: one `runId` or selected `runIds`, with `mode: "any" | "all"`. It returns terminal outcomes plus still-pending IDs; an unsuccessful workflow returns early even in `all` mode. It never chooses a winner or cancels pending work.
|
|
298
|
+
- `wait`: one `runId` or selected `runIds`, with `mode: "any" | "all"`. It returns terminal outcomes plus still-pending IDs; an unsuccessful workflow returns early even in `all` mode. It never chooses a winner or cancels pending work. While waiting, a bounded heartbeat (independent of any single target settling) reports live progress — watched targets, completed/pending counts, and recent activity — through the tool's update channel; it stops automatically on settlement, error, or interruption. Each settled outcome's `result` is spent from one shared byte budget (`limitBytes`, default 32768) across the whole response, in the requested `runId`/`runIds` order — never settlement race order, so the same targets and final states spend the budget identically regardless of which one happened to settle first: a result that fits is returned complete, one that does not is truncated with `resultTruncated: true` and the existing `outputRef`/`diagnosticsRef` to continue reading it — not a fixed-length teaser regardless of size. A target's evidence is never read from disk once the shared budget is already exhausted.
|
|
273
299
|
- `cancel`: one `runId` and optional reason. Whole-workflow cancellation stops active children; targeted child cancellation remains a catchable workflow outcome. Cancellation does not roll back edits or other side effects.
|
|
274
300
|
|
|
275
301
|
Interrupting a blocking `Agent`/`workflow` call cancels its work. Interrupting `external_runs wait` stops only that wait. Background work survives its launching tool return and ordinary parent turns, but not the owning session: orderly session shutdown requests cancellation and waits for bounded cleanup. This is not a daemon. After a host crash or unconfirmed shutdown, unfinished evidence is `interrupted_or_uncertain`; restart restores evidence access, never live ownership or guaranteed retrospective process termination. No routine activity wakes the parent, and live steering is not supported.
|
|
@@ -313,7 +339,9 @@ const [a, b, c] = await parallel([
|
|
|
313
339
|
return { a, b, c };
|
|
314
340
|
```
|
|
315
341
|
|
|
316
|
-
Sequential `await agent(...)` stays serial by construction; `parallel([...])` is how parallel children share the single `maxConcurrentSubagents` limiter at once. Top-level direct `Agent` calls issued together also run together (`executionMode: "parallel"`, default under one limiter). `external_runs wait` and `external_runs inspect` cover both surfaces. Its first statement must be the current declaration `export const meta = { apiVersion: 1, name, description }`; missing or unsupported versions fail before any child launches. Every `agent()` child uses the same `role`/optional `harness` resolution as direct `Agent` calls, with legacy exact `subagent_type` also supported.
|
|
342
|
+
Sequential `await agent(...)` stays serial by construction; `parallel([...])` is how parallel children share the single `maxConcurrentSubagents` limiter at once. Top-level direct `Agent` calls issued together also run together (`executionMode: "parallel"`, default under one limiter). `external_runs wait` and `external_runs inspect` cover both surfaces. Its first statement must be the current declaration `export const meta = { apiVersion: 1, name, description }`; missing or unsupported versions fail before any child launches. Every `agent()` child uses the same `role`/optional `harness` resolution as direct `Agent` calls, with legacy exact `subagent_type` also supported. `agent()`'s second argument accepts `description` as the common task-name option shared with direct `Agent` calls; `label` remains a compatible alias for existing scripts. Setting both to different values is rejected rather than silently preferring one.
|
|
343
|
+
|
|
344
|
+
`background: true` belongs to the `workflow` tool call itself (`workflow({ script, background: true })`), never to `meta` — `meta` fields other than `apiVersion`/`name`/`description`/`phases` are ignored, so `export const meta = { ..., background: true }` does not run the workflow in the background.
|
|
317
345
|
|
|
318
346
|
Saved workflows are discovered on demand through `external_help({ topic: "workflow" })`; project `.pi/workflows` entries are included only when Pi reports the project trusted.
|
|
319
347
|
|
|
@@ -332,8 +360,8 @@ Every child selects from one parent snapshot and effective settings frozen at wo
|
|
|
332
360
|
Every `Agent` call and workflow `agent()` child also accepts these optional run parameters (`context` is covered under agent usage above):
|
|
333
361
|
|
|
334
362
|
- `permission`: `readonly` | `edit` | `danger` (default `danger`). Tiers map onto native harness mechanisms — Claude permission modes and Codex's single-axis `--sandbox`. Antigravity (`agy`) is different: its headless sandbox denies even read-only tools like `read_url_content`, and its only unsandboxed mode is `--dangerously-skip-permissions`, so **every agy run is unsandboxed** and `readonly`/`edit` on agy are advisory profile-body instructions, not a boundary. Getting out of the model's way is deliberate; every agy run discloses as `unsandboxed external CLI` rather than claiming a read-only boundary it cannot keep. Claude `readonly`/`edit` runs auto-deny shell commands headlessly; denials are surfaced in the receipt.
|
|
335
|
-
- `max_budget_usd`: a spending cap. Claude Code enforces it mid-run with its native `--max-budget-usd` flag
|
|
336
|
-
- `resume`: a prior run id. Continues the same backend conversation (Claude `--resume`, Codex `exec resume`, agy `--conversation`) instead of starting from scratch. The prior run must use the same backend. Claude sessions persist in Claude Code's own local storage (this extension no longer passes `--no-session-persistence`) so recorded session ids stay resumable; remove old conversations from Claude Code itself if that matters to you. `resume` cannot be combined with `context` sharing — continue an existing child, or start a new one with a snapshot.
|
|
363
|
+
- `max_budget_usd`: a spending cap. Claude Code enforces it mid-run with its native `--max-budget-usd` flag. Codex estimates cost from a price map and agy does not report cost at all; Grok reports its own native cost (`total_cost_usd`) but exposes no enforcement flag. For codex, agy, and grok alike, the cap is recorded and marked `budget unenforceable` instead of pretended.
|
|
364
|
+
- `resume`: a prior run id. Continues the same backend conversation (Claude `--resume`, Codex `exec resume`, agy `--conversation`, Grok `--resume`) instead of starting from scratch. The prior run must use the same backend. Claude sessions persist in Claude Code's own local storage (this extension no longer passes `--no-session-persistence`) so recorded session ids stay resumable; remove old conversations from Claude Code itself if that matters to you. `resume` cannot be combined with `context` sharing — continue an existing child, or start a new one with a snapshot.
|
|
337
365
|
|
|
338
366
|
Resolution order for tiers and budgets: call > profile frontmatter (`permission:`, `max_budget_usd:`) > settings defaults.
|
|
339
367
|
|
|
@@ -433,21 +461,29 @@ Run the deterministic offline checks:
|
|
|
433
461
|
npm run check
|
|
434
462
|
```
|
|
435
463
|
|
|
436
|
-
Real-provider checks consume tokens
|
|
464
|
+
Real-provider checks consume tokens, but the default lane never prompts a root LLM to choose the tool call: it builds an in-process Pi SDK session with a faux, never-streamed root model and calls the `Agent`/`workflow`/`external_runs` tool executors directly, so only the selected external backend's own child (a real spawned CLI process, or, for `--backend pi`, a real in-process nested Pi child) is real:
|
|
437
465
|
|
|
438
466
|
```bash
|
|
439
|
-
export PI_CODING_AGENT_DIR="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
440
467
|
npm run e2e -- --backend claude
|
|
441
468
|
npm run e2e -- --backend codex
|
|
442
469
|
npm run e2e -- --backend agy
|
|
470
|
+
npm run e2e -- --backend grok
|
|
443
471
|
npm run e2e -- --backend claude --workflow
|
|
444
472
|
npm run e2e -- --backend codex --workflow
|
|
445
473
|
npm run e2e -- --backend agy --workflow
|
|
474
|
+
npm run e2e -- --backend grok --workflow
|
|
446
475
|
npm run e2e -- --backend pi --harness pi-deepseek
|
|
447
476
|
npm run e2e -- --backend pi --harness pi-deepseek --workflow
|
|
448
477
|
```
|
|
449
478
|
|
|
450
|
-
The `pi` backend requires a harness you have already registered yourself in your real `harnesses.json` with real credentials configured; the script never registers or pays for one on your behalf.
|
|
479
|
+
The `pi` backend requires a harness you have already registered yourself in your real `harnesses.json` with real credentials configured; the script never registers, writes to, or pays for one on your behalf. `--interrupt` cancels a backgrounded `Agent` through `external_runs` instead of `--workflow`'s two-child check.
|
|
480
|
+
|
|
481
|
+
A separate, explicit `--routing-smoke` lane spawns a real `pi` CLI process with a real root model and asks it, in plain language, to pick the right tool — useful only when role discovery, tool descriptions, or coordinator guidance changes, and never the default:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
export PI_CODING_AGENT_DIR="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
485
|
+
npm run e2e -- --routing-smoke --backend codex
|
|
486
|
+
```
|
|
451
487
|
|
|
452
488
|
See [`docs/field-testing.md`](docs/field-testing.md) for provider checks and [`docs/releasing.md`](docs/releasing.md) for the release process.
|
|
453
489
|
|
package/docs/field-testing.md
CHANGED
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Real-provider checks consume tokens and normally run external CLIs in dangerous/no-approval modes. Claude uses `auto` permission mode instead when its effective UID is 0. Use them only in a trusted checkout. Receipts can contain prompts and source excerpts despite best-effort redaction.
|
|
4
4
|
|
|
5
|
+
`npm run e2e` has two lanes. The default lane is deterministic and root-model-free: it builds an in-process Pi SDK session with a faux, never-prompted root model and calls the `Agent`/`workflow`/`external_runs` tool executors directly, so tool selection is never left to a live LLM decision — only the selected external backend's own child call (a real spawned CLI process, or, for `--backend pi`, a real in-process nested Pi child) is real. `--routing-smoke` is a separate, explicit lane that spawns a real `pi` CLI process with a real root model and asks it, in plain language, to pick the right tool; see [Natural-language routing smoke](#natural-language-routing-smoke) below. Only `--routing-smoke` needs root Pi authentication or `--root-model`/`--root-thinking`.
|
|
6
|
+
|
|
5
7
|
## Preparation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
cd /path/to/pi-flow-external
|
|
9
|
-
export PI_CODING_AGENT_DIR="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
10
|
-
export ROOT_MODEL="openai-codex/gpt-5.6-sol"
|
|
11
|
-
pi auth check --model "$ROOT_MODEL" --json
|
|
12
11
|
claude --version
|
|
13
12
|
codex --version
|
|
14
13
|
agy --version
|
|
14
|
+
grok --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The default lane needs no root Pi authentication at all (its root model is a faux, never-prompted placeholder). Only `--routing-smoke` needs a working root model:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export PI_CODING_AGENT_DIR="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
21
|
+
export ROOT_MODEL="openai-codex/gpt-5.6-sol"
|
|
22
|
+
pi auth check --model "$ROOT_MODEL" --json
|
|
15
23
|
```
|
|
16
24
|
|
|
17
25
|
Root Pi authentication is separate from each delegated CLI.
|
|
@@ -24,20 +32,23 @@ Run only the backend affected during development:
|
|
|
24
32
|
npm run e2e -- --backend claude
|
|
25
33
|
npm run e2e -- --backend codex
|
|
26
34
|
npm run e2e -- --backend agy
|
|
35
|
+
npm run e2e -- --backend grok
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
Each run creates a collision-safe temporary profile and fixture
|
|
38
|
+
Each run creates a collision-safe (`randomUUID`-named) temporary profile and fixture in an isolated agent directory, calls the `Agent` tool executor directly with an explicit `role`/`harness`, requires the exact expected result in the child's real result, checks for one complete `done` receipt, verifies that the fixture stayed clean, and removes its temporary files. Isolation is unconditional: for every backend above, an inherited `PI_CODING_AGENT_DIR` (such as the one exported in [Preparation](#preparation)) is ignored unless you pass `--agent-dir` explicitly — only `--backend pi` and `--routing-smoke` ever default to your real agent directory, since they only read it and never write into it.
|
|
30
39
|
|
|
31
40
|
Defaults:
|
|
32
41
|
|
|
33
42
|
| Lane | Model | Thinking |
|
|
34
43
|
|---|---|---|
|
|
35
|
-
| Root Pi | `openai-codex/gpt-5.6-sol` | `high` |
|
|
36
44
|
| Claude | `claude-sonnet-5` | `high` |
|
|
37
45
|
| Codex | `gpt-5.6-sol` | `high` |
|
|
38
46
|
| Agy | `gemini-3.7-flash-high` | `high` |
|
|
47
|
+
| Grok | `grok-4.6` | `high` |
|
|
48
|
+
|
|
49
|
+
Override with `--model`/`--thinking`. Use `--keep` only when evidence inspection is necessary; it preserves sensitive output and the temporary profile path printed by the runner.
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
Grok's `readonly`/`edit` tiers run under its own kernel sandbox (`--sandbox read-only`/`workspace`). On a macOS host where `/var/run/docker.sock` resolves to a symlink, sandbox startup can fail closed before the child even runs the prompt — that is expected fail-closed behavior, not a bug; verify enforcement on Linux, or adjust the host's Docker socket if you need to reproduce it locally on macOS.
|
|
41
52
|
|
|
42
53
|
### Named Pi harness receipt
|
|
43
54
|
|
|
@@ -47,7 +58,7 @@ Run only when the pi runtime contract (§7 of the design), harness registry, or
|
|
|
47
58
|
npm run e2e -- --backend pi --harness pi-deepseek
|
|
48
59
|
```
|
|
49
60
|
|
|
50
|
-
This lane
|
|
61
|
+
This lane reads your real agent directory (harnesses.json, subagents) but never writes a temporary profile there — canonical synthesis already provides the `worker` role for any registered harness — and never mutates it: the faux root model is registered only in-process, and the harness's own real model/auth resolve normally through your real `models.json`/`auth.json`. It delegates to the harness you name and requires the same one complete `done` receipt.
|
|
51
62
|
|
|
52
63
|
## Supervised workflow receipt
|
|
53
64
|
|
|
@@ -57,10 +68,11 @@ When workflow runtime, scheduling, supervision, or tool integration changes, run
|
|
|
57
68
|
npm run e2e -- --backend claude --workflow
|
|
58
69
|
npm run e2e -- --backend codex --workflow
|
|
59
70
|
npm run e2e -- --backend agy --workflow
|
|
71
|
+
npm run e2e -- --backend grok --workflow
|
|
60
72
|
npm run e2e -- --backend pi --harness pi-deepseek --workflow
|
|
61
73
|
```
|
|
62
74
|
|
|
63
|
-
Each command
|
|
75
|
+
Each command calls the `workflow` tool executor directly with a script declaring `meta.apiVersion: 1` and running two parallel `agent()` calls, blocking (`background: false`) rather than supervised through `external_runs` — workflow execution itself is what's under test here, not run-observability ceremony (the change-triggered background-run observability check below covers that separately). It requires the workflow to complete with two `done` child receipts, both results containing the expected token, and the read-only fixture staying clean.
|
|
64
76
|
|
|
65
77
|
## Change-triggered interruption and output check
|
|
66
78
|
|
|
@@ -70,15 +82,19 @@ When adapter cancellation, process-tree termination, partial output, or supervis
|
|
|
70
82
|
npm run e2e -- --backend claude --interrupt
|
|
71
83
|
npm run e2e -- --backend codex --interrupt
|
|
72
84
|
npm run e2e -- --backend agy --interrupt
|
|
85
|
+
npm run e2e -- --backend grok --interrupt
|
|
73
86
|
```
|
|
74
87
|
|
|
75
|
-
The runner
|
|
88
|
+
The runner calls the `Agent` tool executor directly with `background: true`, polls `external_runs` until the run is observed `running`, cancels its stable ID with an explicit reason, waits for its `cancelled` outcome, and asks `external_runs` for every available output and diagnostic page. A very early cancellation may legitimately have diagnostics but no assistant text; the durable receipt must still be `aborted` with the exact reason. Do not retry a failure automatically. Use `--keep` for one deliberate evidence inspection, then remove the printed run root and temporary profile.
|
|
76
89
|
|
|
77
90
|
## Natural-language routing smoke
|
|
78
91
|
|
|
79
|
-
|
|
92
|
+
There are two levels of routing check:
|
|
93
|
+
|
|
94
|
+
- `npm run e2e -- --routing-smoke --backend <claude|codex|agy|grok>` (optionally `--workflow`/`--interrupt`) spawns a real `pi` CLI process with a real root model and gives it a single plain-language instruction naming the exact tool, role, and harness to call. It is a mechanical, automated check that the natural-language call-and-relay path (real root LLM → real tool call → real backend child) still works end to end. It requires real root Pi authentication (see Preparation) and is opt-in only — never the default backend gate — because the root model's tool-call decision is a live, provider-billed, non-deterministic step. `--root-model`/`--root-thinking` only apply here.
|
|
95
|
+
- The broader, still-manual qualitative check below covers *discovery*, not just one named call: when role discovery, tool descriptions, or coordinator guidance changes, run three fresh Pi sessions against the current checkout and a read-only fixture. Ask for two named harnesses to review, two named harnesses to research, and a task split between Pi plus two named harnesses. For each session, verify that each requested external harness produced one complete `done` receipt through `role` plus `harness`, no help/discovery call was needed for the built-in roles, and the fixture stayed unchanged.
|
|
80
96
|
|
|
81
|
-
|
|
97
|
+
Neither is a statistical regression comparison or a model-independent guarantee. Neither covers workflow routing or the omitted-`harness` default; use the workflow receipt above and a separate direct role request without `harness` for those paths.
|
|
82
98
|
|
|
83
99
|
## Change-triggered nested timeout check
|
|
84
100
|
|
|
@@ -125,10 +141,10 @@ Run only when project default-harness resolution changes. In a fresh Pi session
|
|
|
125
141
|
Before a runtime release:
|
|
126
142
|
|
|
127
143
|
1. Run `npm run check`.
|
|
128
|
-
2. Run all
|
|
129
|
-
3. Run all
|
|
144
|
+
2. Run all four direct backend receipts.
|
|
145
|
+
3. Run all four supervised workflow receipts.
|
|
130
146
|
4. Run interruption checks for adapters whose cancellation/output path changed.
|
|
131
|
-
5. Run the
|
|
147
|
+
5. Run `--routing-smoke` (mechanical) and the manual qualitative routing smoke only if role discovery, tool descriptions, or coordinator guidance changed.
|
|
132
148
|
6. Run the nested timeout check only if nested detection or timeout behavior changed.
|
|
133
149
|
7. Run the background-run observability check only if batch inspection, the `final` view, timing projection, or `/external runs` browsing changed.
|
|
134
150
|
8. Remove temporary evidence and profiles.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tranhoangnguyen0310/pi-flow-external",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "External Claude Code, Codex CLI, and
|
|
3
|
+
"version": "2.3.1-external.0",
|
|
4
|
+
"description": "External Claude Code, Codex CLI, Antigravity, and Grok Build CLI delegation for pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"exports": "./index.ts",
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
"codex",
|
|
29
29
|
"claude",
|
|
30
30
|
"agy",
|
|
31
|
-
"antigravity"
|
|
31
|
+
"antigravity",
|
|
32
|
+
"grok"
|
|
32
33
|
],
|
|
33
34
|
"license": "MIT",
|
|
34
35
|
"scripts": {
|
|
35
36
|
"build": "tsc",
|
|
36
37
|
"check": "tsc --noEmit && vitest run",
|
|
37
|
-
"e2e": "node scripts/e2e/external.mjs",
|
|
38
|
+
"e2e": "node --experimental-transform-types scripts/e2e/external.mjs",
|
|
38
39
|
"field-report": "node scripts/field-report.mjs",
|
|
39
40
|
"test": "vitest run"
|
|
40
41
|
},
|
package/scripts/field-report.mjs
CHANGED
|
@@ -57,7 +57,7 @@ async function loadRecord(baseDirectory, runId) {
|
|
|
57
57
|
const eventCount = finite(document.eventCount);
|
|
58
58
|
const attemptedEventCount = finite(document.attemptedEventCount);
|
|
59
59
|
const backendEventCount = events.filter((event) => event.type === "backend_event").length;
|
|
60
|
-
if (!["claude", "codex", "agy"].includes(summary.backend)) problems.push("missing or unknown backend");
|
|
60
|
+
if (!["claude", "codex", "agy", "grok"].includes(summary.backend)) problems.push("missing or unknown backend");
|
|
61
61
|
if (typeof summary.profile !== "string" || !summary.profile) problems.push("missing profile");
|
|
62
62
|
if (!["done", "error", "aborted"].includes(summary.status)) problems.push("missing or unknown status");
|
|
63
63
|
if (writeErrorCount === undefined) problems.push("missing write error count");
|