@llblab/pi-kit 0.35.5 → 0.36.1

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.
Files changed (34) hide show
  1. package/AGENTS.md +2 -1
  2. package/BACKLOG.md +0 -5
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +33 -31
  5. package/node_modules/@llblab/pi-actors/AGENTS.md +3 -1
  6. package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -0
  7. package/node_modules/@llblab/pi-actors/README.md +2 -2
  8. package/node_modules/@llblab/pi-actors/dist/index.js +1 -0
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +1 -0
  10. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +3 -0
  11. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +7 -2
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +20 -6
  13. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +1 -0
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +74 -74
  15. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  16. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +1 -1
  17. package/node_modules/@llblab/pi-actors/docs/README.md +1 -1
  18. package/node_modules/@llblab/pi-actors/docs/async-runs.md +3 -1
  19. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +22 -20
  20. package/node_modules/@llblab/pi-actors/index.ts +1 -0
  21. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +4 -0
  22. package/node_modules/@llblab/pi-actors/lib/pi.ts +33 -9
  23. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +72 -69
  24. package/node_modules/@llblab/pi-actors/package.json +1 -1
  25. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  26. package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +1 -1
  27. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -0
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +5 -4
  29. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  30. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
  31. package/node_modules/@llblab/pi-state-flow/docs/usage.md +1 -0
  32. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +5 -4
  33. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  34. package/package.json +3 -3
package/AGENTS.md CHANGED
@@ -27,7 +27,8 @@
27
27
  - Do not copy extension source, Skills, or documentation into this repository.
28
28
  - Preserve package independence: a kit release may advance any subset of included extensions without forcing lockstep extension releases.
29
29
  - Adding packages or newly exposed capabilities requires a kit minor bump. For updates to the existing composition, derive the kit version bump from the highest dependency bump in the synchronized release cohort: dependency patch-only updates require a kit patch bump, while any dependency minor update requires a kit minor bump. Do not let lower-level dependency bumps force a larger kit bump.
30
- - Automated dependency updates must remain reviewable PRs with a visible kit version bump; never auto-merge. Keep unreviewed preparation read-only and secret-free, isolate PR writes on a fresh runner, and accept only declared data artifacts without executing them. Publish only the reviewed main commit after clean validation, and never overwrite release tags.
30
+ - Stable patch/minor updates within the current major of existing direct packages are explicitly authorized to commit coherent exact pins and a visible kit bump directly to main, without PRs or manual approval. Major/prerelease releases and composition/resource changes remain manual.
31
+ - Keep dependency preparation read-only and secret-free; isolate main writes on a fresh runner and accept only declared data artifacts without executing them. Never force or rebase over concurrent main work. Explicitly dispatch the OIDC publisher for the expected commit, validate again before publication, and never overwrite release tags.
31
32
  - Use English for public and repository-facing text.
32
33
  - Do not commit, publish, tag, create a remote repository, or mutate Pi settings without explicit authorization.
33
34
 
package/BACKLOG.md CHANGED
@@ -2,11 +2,6 @@
2
2
 
3
3
  Package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
4
4
 
5
- ## Automation activation (administrator-owned)
6
-
7
- - Before relying on automated updates/publication, configure the repository settings described in [Update automation](./README.md#repository-setup-and-secrets): required human Code Owner approval, stale-approval dismissal, the `validate` PR check, no bypass/auto-merge and Actions PR creation permission. Add an automation-account `PI_KIT_PAT` only if downstream checks must trigger automatically. Local validation does not establish these remote settings.
8
- - **First OIDC publication:** The operator created the `publish-pi-kit.yml` npm trusted publisher on October 21, 2026. Confirm direct `npm publish` is permitted and complete the first successful workflow publication before **October 23, 2026 at 18:13 UTC** to validate that binding; otherwise recreate it. Keep the existing `release.yml` connection until success. No `NPM_TOKEN` is required.
9
-
10
5
  ## Carried checks
11
6
 
12
7
  - **Installed 0.35.4 Telegram smoke (operator-owned):** After separately authorized kit installation/reload of every Pi process together, run `/telegram-new` in one follower instance and confirm the same Thread is reused with a new letter slot and title, with no second tab and no orphan left behind. Then restart that instance, open a new session in the same folder, and confirm it adopts the retained Thread instead of provisioning another. Do not erase accepted work or replay uncertain input. Packed validation does not certify installed clients.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.36.1: Bundled Package Updates (2026-10-22)
6
+
7
+ - `Exact pins`: `@llblab/pi-state-flow` 0.26.4 → 0.26.5.
8
+ - `Packaging`: Synchronizes the lockfile, bundle aliases and README inventory. Package membership and resource order remain unchanged; packed validation does not certify extension safety or installed-client behavior.
9
+
10
+ ## 0.36.0: Bundled Package Updates (2026-10-21)
11
+
12
+ - `Automatic updates`: Replaces the dependency PR/approval step with validated direct-to-main adoption of stable same-major patch/minor releases. Exact pins, bundle aliases, lockfile, README and kit version remain synchronized; preparation stays read-only and a fresh data-only writer rejects unsafe payloads and concurrent main changes.
13
+ - `Release handoff`: Explicitly dispatches the OIDC publisher with the committed SHA because GITHUB_TOKEN pushes do not trigger ordinary push workflows. The publisher rejects a different commit before tagging. No updates means no fabricated commit or release; existing untagged versions can retry a missed handoff. Major/prerelease and composition changes remain manual; packaging checks do not establish extension safety.
14
+ - `Exact pins`: `@llblab/pi-actors` 0.54.0 → 0.55.0.
15
+ - `Packaging`: Synchronizes the lockfile, bundle aliases and README inventory. Package membership and resource order remain unchanged; packed validation does not certify extension safety or installed-client behavior.
16
+
5
17
  ## 0.35.5: Reviewed Updates and OIDC Releases
6
18
 
7
19
  - `Update preparation`: Adds daily/manual npm discovery and one human-reviewed update PR containing exact pins, both bundle aliases, regenerated lockfile, README inventory, kit version and changelog. Preparation is read-only without secrets or dependency hooks/bin links; a fresh write runner accepts only four checked data files. Major/prerelease updates and resource changes remain manual; failed validation prevents PR creation, and no auto-merge is enabled.
package/README.md CHANGED
@@ -12,12 +12,12 @@ Package links lead to the owning repositories for usage, documentation, issues,
12
12
 
13
13
  | Package | Version | Purpose |
14
14
  | --- | ---: | --- |
15
- | [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.54.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
15
+ | [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.55.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
16
16
  | [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.2` | Claude subscription quota status and shared per-model Fast toggle for Opus, mirrored in Telegram |
17
17
  | [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.0` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
18
18
  | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.1` | OpenAI/legacy Codex subscription quota and Business credit status, with persistent priority Fast toggle mirrored in Telegram |
19
19
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
20
- | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.4` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
20
+ | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.5` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
21
21
  | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.57.3` | Telegram companion with per-prompt Steer, queued controls, Reroute/Restore, recoverable connections, previews, follower Threads, files, and voice |
22
22
  | [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
23
23
 
@@ -25,7 +25,7 @@ Versions are exact by design. An upstream release does not change an installed k
25
25
 
26
26
  ## Install
27
27
 
28
- Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.26.4/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
28
+ Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.26.5/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
29
29
 
30
30
  From npm:
31
31
 
@@ -60,51 +60,53 @@ The repository-local `.npmrc` keeps Pi-provided peer packages out of the kit's l
60
60
 
61
61
  ## Update automation
62
62
 
63
- Two independent workflows prepare updates and publish **human-reviewed** kit versions. Automation reduces synchronization work; it does not decide whether an extension is safe and never enables auto-merge.
63
+ Updates are **fully automatic for newer stable patch/minor releases within each existing package's current major**. The updater validates and commits the coherent update directly to `main`, then requests publication. There is no dependency PR, merge step or manual approval. This is an explicit maintainer policy accepting the risk of upstream code; exact versions and successful packaging checks do not establish extension safety.
64
64
 
65
- ### Prepare an update PR
65
+ ### Daily update
66
66
 
67
- [Update pi-kit dependencies](./.github/workflows/update-pi-kit-deps.yml) runs daily at **03:00 UTC** and supports `workflow_dispatch` on `main`. Manual dispatch defaults to `dry_run: true`: it prepares and validates the complete working-tree diff and prints it in the run log/summary, but does not push a branch or create/update a PR. Select `false` to open or refresh the single `chore/pi-kit-deps` PR, labelled `dependencies` and `automated`.
67
+ [Update pi-kit dependencies](./.github/workflows/update-pi-kit-deps.yml) runs daily at **03:00 UTC** and supports `workflow_dispatch` on `main`. Manual dispatch defaults to `dry_run: true`: prepare and validate the complete diff, show the report and retain the data artifact, but do not commit or publish. Select `false` for the live automatic path.
68
68
 
69
- The dependency-free [updater](./scripts/check-updates.mjs) reads npm's `latest` metadata for the existing direct dependencies. A custom updater is used instead of Dependabot because one coherent PR must synchronize kit-owned artifacts, not just npm pins:
69
+ The dependency-free [updater](./scripts/check-updates.mjs) reads npm's `latest` metadata for existing direct dependencies. A custom updater, rather than Dependabot, keeps one coherent update cohort:
70
70
 
71
- - Exact dependency versions, both `bundledDependencies`/`bundleDependencies` aliases, and `package-lock.json` regenerated by `npm install --ignore-scripts --bin-links=false`.
72
- - The README package/version inventory and versioned upstream documentation links.
73
- - The kit version and [changelog](./CHANGELOG.md): patch-only direct updates produce a kit patch; any direct minor update produces a kit minor. Transitive bumps do not determine the kit bump; pending `Unreleased` outcomes are carried into the new release within the changelog budget.
74
- - Existing package membership, extension pointers and resource order are preserved. Tests reject changed upstream resource declarations; prepare those composition changes manually rather than guessing new entrypoints.
71
+ - Exact dependency pins, both `bundledDependencies`/`bundleDependencies` aliases, and `package-lock.json` regenerated by `npm install --ignore-scripts --bin-links=false`.
72
+ - The README package/version inventory, versioned upstream documentation links, and [changelog](./CHANGELOG.md).
73
+ - The kit version: patch-only direct updates produce a kit patch; any direct minor update produces a kit minor. Transitive bumps do not determine the kit bump. Pending `Unreleased` outcomes move into the new release within the changelog budget.
74
+ - Existing package membership, extension pointers and resource order. Major updates, non-stable `latest` versions and changed resource declarations remain manual; automation does not guess a new composition.
75
75
 
76
- Major updates and non-stable `latest` versions are reported for manual preparation, never silently accepted. Pre-1.0 minor releases can still break compatibility and require particular care. After synchronization, the updater runs **`npm ci --ignore-scripts --bin-links=false` and `npm run validate`**. Failure stops the run before any PR write; inspect the failed run's log. Both installs disable dependency lifecycle hooks and executable links, so a dependency-provided `node`/`npm` bin cannot shadow validation commands. This is not a security verdict about the bundled code. For a read-only local discovery plan, run `node scripts/check-updates.mjs --plan`; `npm run deps:check` instead prepares and validates changes locally and should be used on a disposable working branch.
76
+ Preparation runs in a **read-only job without repository secrets**. Both installation and clean `npm ci --ignore-scripts --bin-links=false` disable dependency lifecycle hooks and executable links, then `npm run validate` checks the package and bundled inventory. Failure stops before any main write.
77
77
 
78
- Preparation runs in a **read-only job without repository secrets**. A separate fresh runner receives only `package.json`, `package-lock.json`, `README.md` and `CHANGELOG.md` from that run's artifact. Before any PR write, the trusted updater rejects extra paths, symlinks, package-set/resource/script changes, incorrect version bumps and unexpected prose edits; it regenerates the PR report itself. The write job never installs dependencies or executes artifact-provided scripts. Read-only preparation cannot poison the later write runner's command path.
78
+ A **separate fresh writer runner** receives only `package.json`, `package-lock.json`, `README.md` and `CHANGELOG.md` from that run's artifact. The trusted updater rejects extra paths, symlinks, script/resource/package-set changes, hidden rollbacks, incorrect kit bumps and unexpected prose edits. It does not install dependencies or execute transferred scripts. The writer requires a clean tracked checkout at the same main baseline, commits only those four files, and uses a normal push to `main`. A concurrent main change rejects the update; it never force-pushes, rebases or bypasses protection.
79
79
 
80
- ### Maintainer review
80
+ The report lists exact `old → new` pins and upstream release/compare links for audit. A read-only local discovery plan is available through `node scripts/check-updates.mjs --plan`; `npm run deps:check` prepares and validates local changes but does not push them. Internal `--apply-prepared` is the privileged clean-runner commit/push path, not a normal local update command.
81
81
 
82
- For every bot PR, use the [Security](#security) guidance and its generated checklist:
82
+ ### Automatic publication
83
83
 
84
- 1. Read the linked upstream releases, changelogs and source comparisons. Investigate breaking changes and compatibility with the supported Pi/Node versions.
85
- 2. Review new transitive dependencies, registry integrity changes, executable hooks and auxiliary workers/scripts. Confirm pointer-loaded extensions resolve auxiliary files from their own `import.meta.url`, not the redirected entry path.
86
- 3. Inspect the entire diff: exact pins with no ranges, both bundle aliases matching the package set, lockfile, README inventory, kit version and changelog. No new package, capability or resource should enter through an automatic pin-only update.
87
- 4. Inspect successful `validate` output, including the dry-run tarball's bundled manifests, extension targets and Skills. Run necessary isolated runtime/client smoke checks; package checks and a clean audit do not establish extension safety.
88
- 5. Obtain human approval, then **merge manually**. Do not enable auto-merge. If the updater refreshes the PR, review the new diff again before approval.
84
+ Pushes made with `GITHUB_TOKEN` do not trigger another workflow's ordinary `push` event. The writer therefore explicitly dispatches [Publish pi-kit](./.github/workflows/publish-pi-kit.yml) using `workflow_dispatch` and an `expected_commit` SHA. GitHub permits this dispatch exception with `GITHUB_TOKEN`; no PAT is needed. The publisher rejects a different main commit before tag creation, even if the version would otherwise no-op.
89
85
 
90
- The existing [Release / validate](./.github/workflows/release.yml) PR check is retained, without a tag-triggered publisher. PRs created with `GITHUB_TOKEN` normally do **not** trigger another workflow. The updater already validates before creating them; to obtain the separate PR check, a maintainer can close/reopen the PR or push a reviewed change with their own credentials. `workflow_dispatch` on the PR branch can also provide separate validation evidence, but it does not replace a required PR check. Use an automation-account `PI_KIT_PAT` when automatic downstream PR checks are needed; do not use the sole reviewer's PAT, since authors cannot approve their own PRs.
86
+ On a live run with no new pins, the fresh job still checks for an untagged main version awaiting publication. This can retry a failed handoff before any tag exists; an unchanged already-tagged version does not manufacture a commit or release. The updater reports that publication was requested, not that it succeeded: inspect the separate publisher run for the final result.
91
87
 
92
- ### Publish after merge
88
+ The publisher also supports ordinary human pushes to `main` changing `package.json` and manual dispatch on `main`. It **reads, never bumps**, the kit version. If `v<version>` equals the latest `v*` tag in version-sort order, it exits successfully without republishing. Older versions and existing non-latest tags are rejected, not overwritten.
93
89
 
94
- [Publish pi-kit](./.github/workflows/publish-pi-kit.yml) runs on `main` pushes changing `package.json`, or by manual dispatch on `main`. It **reads**, never bumps, the reviewed kit version. If `v<version>` equals the latest `v*` tag in version-sort order, the run exits successfully without publishing. Older versions and existing non-latest tags are rejected, not overwritten.
90
+ For a new version the order is: verify the hosted main-workflow OIDC environment and npm >= 11.5.1 → clean `npm ci --ignore-scripts --bin-links=false` → `npm run validate` → create and push `vX.Y.Z` → `npm publish --access public` through Trusted Publishing → verify public npm identity and packed inventory → `gh release create --generate-notes`. Publication is serialized. An npm version-conflict is accepted only when the tag and npm `gitHead` both match HEAD; authentication, staging-only permission and contradictory identities remain errors.
95
91
 
96
- For a new version the order is: verify the hosted main-workflow OIDC environment and npm >= 11.5.1 → clean `npm ci --ignore-scripts --bin-links=false` → `npm run validate` → create and push `vX.Y.Z` → `npm publish --access public` through Trusted Publishing → verify public npm identity and packed inventory → `gh release create --generate-notes`. Validation must succeed before a tag is created. The tag push is non-forced, concurrent publications are serialized, and an npm version-conflict error is accepted only when the tag and npm `gitHead` both match the current HEAD. Authentication, registry failures and contradictory identities remain errors. The workflow never commits to `main`.
92
+ A tag may remain if npm or GitHub fails **after** successful validation and tag push. A same-version rerun intentionally no-ops and does not automatically repair this partial state. Recovery requires a separate maintainer decision: verify the existing remote tag and npm state, preserve the exact tagged commit, and repeat clean validation before any missing publication. Use an explicitly reviewed OIDC recovery or interactive maintainer publication with 2FA; never silently fall back to a persistent token or staging. Verify the public package before creating any missing GitHub Release. Never move/delete a published tag or reuse a version for different content.
97
93
 
98
- A tag may remain if npm or GitHub fails **after** successful validation and tag push. An ordinary rerun intentionally no-ops once that version is the latest tag; it does not republish. Recovery requires a separate maintainer decision: verify the existing remote tag and npm state, preserve the exact tagged commit, and rerun `npm ci --ignore-scripts --bin-links=false` and `npm run validate` before any missing publication. Use an explicitly reviewed OIDC recovery or an interactive maintainer publication with 2FA; never silently fall back to a persistent token or staging. Run `node scripts/publish-pi-kit.mjs verify` before creating a missing GitHub Release with `gh release create vX.Y.Z --verify-tag --generate-notes`. Never move/delete a published tag or reuse a version for different content.
94
+ ### Manual changes and operations
95
+
96
+ Major/prerelease adoption, adding packages, changing resources and editing automation remain manual engineering work. [Release / validate](./.github/workflows/release.yml) still checks normal PRs; [CODEOWNERS](./.github/CODEOWNERS) requests owner review for workflows, manifests and scripts. CODEOWNERS does not gate the expressly authorized direct dependency updates.
97
+
98
+ Inspect failed workflow logs and upstream source/changelogs when validation fails or runtime behavior is suspect. Pre-1.0 minor versions can break compatibility. Check supported Pi/Node versions, transitive dependencies, executable hooks, pointer targets and auxiliary-file resolution from the owning package's `import.meta.url` when preparing manual changes. Packed validation does not certify installed-client rendering or runtime safety.
99
+
100
+ Disable the updater workflow before manually holding or rolling back a problematic pin, otherwise a later automatic run may advance it again. Publish corrective content at a new kit version; never retag or replace an npm version. Pi installation and live-client reload remain separate operator actions.
99
101
 
100
102
  ### Repository setup and secrets
101
103
 
102
- - **npm Trusted Publishing (required; no `NPM_TOKEN`):** In the npm settings for `@llblab/pi-kit`, add a GitHub Actions trusted publisher for user/organization `llblab`, repository `pi-kit`, workflow filename `publish-pi-kit.yml` (without `.github/workflows/`), and no environment name. Explicitly allow **`npm publish`**, not only `npm stage publish`. Keep an existing `release.yml` trust connection until the new publisher succeeds. A new configuration must complete its first successful publication within two days or be recreated. The preflight verifies runtime prerequisites, not npm authorization; only the successful publish validates the binding. See [npm's Trusted Publishing documentation](https://docs.npmjs.com/trusted-publishers/).
103
- - **`PI_KIT_PAT` (optional):** A repository-scoped automation-account token with contents and pull-request write access. Without it, PR writes and tag pushes use `GITHUB_TOKEN`; those events do not trigger downstream workflows. Use the PAT only if those downstream events are required. No token is stored in source or committed git configuration.
104
- - **Actions permissions:** Permit Actions to create pull requests in the repository settings. Only the updater's fresh PR-write job requests `contents: write` and `pull-requests: write`; dependency preparation and PR validation are read-only. The publisher requests `contents: write` for the validated tag/GitHub Release and `id-token: write` for short-lived npm OIDC authentication on a GitHub-hosted runner. No long-lived npm credential is needed.
105
- - **Do not substitute stage-only tokens:** npm is removing granular-token direct publication in January 2027. A stage-only token returns `E_STAGE_REQUIRED` for `npm publish` and requires separate maintainer approval with 2FA after `npm stage publish`. That is a different release process; this workflow keeps automatic post-merge publication through explicitly permitted OIDC and never silently stages instead.
106
- - **Required human review:** [CODEOWNERS](./.github/CODEOWNERS) assigns `@llblab` to workflows, manifests and automation scripts. An administrator must separately protect `main` with a pull-request requirement, required Code Owner approval, stale-approval dismissal after new commits, and the `validate` status check; disallow bypasses and auto-merge. CODEOWNERS alone does **not** enforce approval. Configure these settings before relying on the pipeline as a mandatory review gate.
104
+ - **npm Trusted Publishing (required; no `NPM_TOKEN`):** Configure the npm package for GitHub user/organization `llblab`, repository `pi-kit`, workflow filename `publish-pi-kit.yml` (without `.github/workflows/`), and no environment name. Explicitly allow **`npm publish`**, not only staging. A new binding must complete its first successful publication within two days or be recreated. Preflight checks runtime prerequisites, not registry authorization. See [npm's Trusted Publishing documentation](https://docs.npmjs.com/trusted-publishers/).
105
+ - **Actions permissions:** Preparation and PR validation are read-only. Only the fresh updater writer requests `contents: write` and `actions: write` for the main push and explicit publisher dispatch; it does not request PR permissions. Repository-wide default token permissions may remain read-only because jobs declare their needs. The publisher requests `contents: write` for the tag/GitHub Release and `id-token: write` for short-lived OIDC.
106
+ - **Main protection:** The repository must permit the updater's authorized main push. Required-PR or signing rules can reject it; automation fails rather than weakening rules, adding bypasses or force-pushing. Any compatibility decision about those protections is administrator-owned. The former Actions create-PR checkbox is no longer required by this updater.
107
+ - **`PI_KIT_PAT` (optional, publisher tag only):** Only needed if tag pushes must trigger other downstream workflows. The updater always uses `GITHUB_TOKEN` plus explicit dispatch, not a PAT. No token is committed to source or git configuration.
108
+ - **Do not substitute stage-only tokens:** Granular-token direct publication is being removed in January 2027. Stage-only publication requires separate maintainer promotion with 2FA; this pipeline instead uses explicitly permitted direct OIDC publication and never silently stages.
107
109
 
108
110
  ## Security
109
111
 
110
- Pi extensions execute with the user's system permissions, and Skills can guide executable actions. Review each included package and release before advancing its pin; the [maintainer checklist](#maintainer-review) makes that review explicit for automated PRs. Automated validation and npm audit are packaging signals, not approval to trust an update. Private Knowledge, credentials, and personal Pi configuration are not bundled.
112
+ Pi extensions execute with the user's system permissions, and Skills can guide executable actions. This repository explicitly opts into adopting supported upstream releases **without human review before publication**. Validation and npm audit are packaging signals, not a security decision; reproducible exact pins do not make new code trustworthy. Disable automatic updates if human source review is required. Private Knowledge, credentials and personal Pi configuration are not bundled.
@@ -140,7 +140,9 @@ Tool result/error text contributes exactly one leading line break. Keep model-fa
140
140
 
141
141
  File watchers accelerate reconciliation; a bounded interval recovers missed terminal and retained-attention events. Canonical attention observation uses stable retained ids and bounds seen memory to the current suffix across compaction; only allowlisted legacy outbox fallback uses line offsets. Ordinary `notify` and `followup` hints prime at startup without replay and require durable state or artifacts for recovery. Explicit `steer` instead re-enters through the owner journal until exact presentation records its newer generation-fenced Trace marker.
142
142
 
143
- Ordinary terminals retain inherited delivery lineage from each nested Actor process to the top-level coordinator. Only that root coordinator may schedule completion follow-ups; descendant sessions stay quiet, descendants wait for their containing top-level Run to terminate, and the complete ready forest is flattened into one immutable settled/idle batch with parent-child rows and bounded semantic outputs. The same batch renders as one visible operator card and one model prompt. Batch delivery is generation-fenced and bounded at 256 durable members, 64 model-facing rows, 16 KiB of context, and a 1 MiB journal. Successful `sendMessage()` acceptance durably finalizes the completion batch and authorizes exact-generation handled markers, so a missed context callback cannot block later completion epochs. Send failure retains the pending batch for retry; queued recovery remains only for the narrow crash boundary between transport acceptance and finalization. Restart recovery walks the bounded active Pi session parent chain: exact queued evidence waits without resend, proven absence returns the envelope to pending, and incomplete or conflicting evidence remains queued with diagnostics. Retry duplicates collapse before presentation, malformed envelopes fail closed, and automatic review waits behind an active completion batch.
143
+ Ordinary terminals retain inherited delivery lineage from each nested Actor process to the top-level coordinator. Only that root coordinator schedules completion delivery; descendant sessions stay quiet and wait for their complete containing top-level tree. A completed active `turn_end` snapshots the ready forest into one immutable batch, preserves earlier boundary entries, and returns one visible custom-message entry plus one continuation, never a native steering/follow-up enqueue. Aborted/error boundaries and pending native input defer projection. Already-idle Pi retains debounced follow-up delivery. The same batch renders as one operator card and one model prompt, fenced by owner and generation and bounded at 256 durable members, 64 model-facing rows, 16 KiB of context, and a 1 MiB journal.
144
+
145
+ Returning a boundary draft is not acceptance: keep it pending until exact model context or bounded active-session parent-chain evidence proves the committed entry. Successful idle `sendMessage()` acceptance finalizes immediately; durable queued phase also proves acceptance. Only proven absence permits retry; incomplete or conflicting pending evidence stays pending with diagnostics. Exact acceptance authorizes generation-fenced terminal handled markers and releases later epochs without depending on one context callback. Retry duplicates collapse before presentation, malformed envelopes fail closed, and automatic review waits behind an active completion batch.
144
146
 
145
147
  When deferred Run results gate the next step, wait for their completion batch. Inspect early only for operator request, meaningful attention, or diagnosis of an overdue Run.
146
148
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.55.0: Active Completion Overlays
6
+
7
+ - `Active Completion Overlays`: Batches ready Actor trees at the next completed model-step boundary, after the full tool batch, so the running coordinator receives fresh results before idle without native steering. Preserves prior boundary entries, exact owner/generation fences, and debounced idle wakeups. Proposed entries remain pending until exact context or bounded session evidence proves acceptance; dropped proposals retry without premature handled markers or duplicate accepted reports.
8
+
5
9
  ## 0.54.0: Pi 1.0 Baseline and Package Licensing
6
10
 
7
11
  - `Licensing`: Includes the MIT LICENSE in source checkouts and npm packages, preserving existing author attribution.
@@ -15,7 +15,7 @@ An **actor** is any runnable local capability: a script, tool, service, pipeline
15
15
 
16
16
  Multi-instance systems commonly put instance creation and routing in an external gateway. pi-actors supports a different topology: the current Pi instance remains the coordinator, companion extensions such as Telegram provide presence, and explicit Runs perform bounded delegated work. The coordinator receives high-level outcomes, decomposes them, stays available for decisions, and owns integration plus final validation instead of becoming another undifferentiated worker.
17
17
 
18
- This topology does not require every task to become a subagent. Short work with one natural validation boundary stays inline; delegation pays when clean context, asynchronous execution, independent judgement, parallel ownership, or coordinator availability exceeds its coordination cost. Bounded implementation can run with reasoning off while the coordinator selectively launches clean-context reasoning-enabled reviewers; several independent reviews can provide broader evidence than one author self-review. Settled completion batches, durable Trace, and declared artifacts replace tight polling loops.
18
+ This topology does not require every task to become a subagent. Short work with one natural validation boundary stays inline; delegation pays when clean context, asynchronous execution, independent judgement, parallel ownership, or coordinator availability exceeds its coordination cost. Bounded implementation can run with reasoning off while the coordinator selectively launches clean-context reasoning-enabled reviewers; several independent reviews can provide broader evidence than one author self-review. Boundary-delivered completion batches, durable Trace, and declared artifacts replace tight polling loops.
19
19
 
20
20
  ## Install
21
21
 
@@ -153,7 +153,7 @@ Trace fields are exact: `id`, `ts`, `kind`, and optional `summary`, `data`, `lev
153
153
 
154
154
  Generic command lifecycle is Trace-only: runner-owned `command.done` records preserve completion and execution evidence but never request or project attention, including malformed legacy records carrying `steer`. There is no Recipe-level command-completion delivery switch. Semantic attention is explicit: `notify` shows visible status without a model turn, `followup` supplies ordinary checkpoint context, and `steer` requests urgent delivery at Pi's next safe assistant/tool boundary. `notify` and `followup` remain retained wake hints that may compact away, so persist recovery state or an artifact first. `steer` is admitted to the bounded owner delivery journal before `deliverAs: "steer"`, acknowledged only in exact model-bound context, and never suppresses the eventual root-terminal batch. Store large evidence in artifacts or bounded execution captures.
155
155
 
156
- Ordinary finite Runs project terminal results through one root-coordinator completion scheduler. Nested actors inherit durable parent-generation lineage: their own Pi sessions never emit completion turns, their results wait until the containing top-level Run terminates, and the ready trees join other top-level completions accumulated during the same active/idle epoch. One immutable bounded forest causes one automatic agent turn after the root Pi settles. The operator sees one gray completion card, while the agent receives the same parent-child tree with bounded actor outputs as one prompt; terminal generations are acknowledged after Pi durably accepts the exact batch, preventing one missed context callback from blocking later completion epochs. Sequence, parallel, repeat, and imported branches remain internal, while separately launched Runs retain exact independent generations. Silent Runs and synchronous stop acknowledgments remain non-projecting.
156
+ Ordinary finite Runs project terminal results through one root-coordinator completion scheduler. Nested actors inherit durable parent-generation lineage: their own Pi sessions never emit completion turns, and their results wait for the complete containing top-level tree. While the root Pi is active, the next completed `turn_end` boundary snapshots all ready trees into one immutable bounded batch, appends one visible custom message after the full tool batch, and requests one next model step. This overlay uses boundary entries, not Pi's native steering or follow-up queue, so the model can integrate fresh results before its current run settles. Already-idle Pi retains a short debounce and one automatic follow-up wake. The operator and model receive the same parent-child tree with bounded actor outputs. Proposed boundary entries stay pending until exact model context or bounded active-session evidence proves acceptance; an idle send finalizes on successful Pi acceptance without requiring a later context callback. Sequence, parallel, repeat, and imported branches remain internal, while separately launched Runs retain exact independent generations. Silent Runs and synchronous stop acknowledgments remain non-projecting.
157
157
 
158
158
  ## Control
159
159
 
@@ -12,6 +12,7 @@ export default function toolRegistryExtension(pi) {
12
12
  pi.on("resources_discover", async () => runtime.discoverResources(import.meta.url));
13
13
  }
14
14
  pi.on("session_start", async (_event, ctx) => runtime.onSessionStart(ctx));
15
+ pi.on("turn_end", (event, ctx) => runtime.onTurnEnd(event, ctx));
15
16
  pi.on("agent_settled", async (_event, ctx) => runtime.onAgentSettled(ctx));
16
17
  pi.on("context", async (event, ctx) => ({
17
18
  messages: runtime.onContext(event.messages, ctx),
@@ -14,6 +14,7 @@ export interface ActorExtensionRuntime {
14
14
  } | undefined;
15
15
  getRunOwnerId(ctx: Pi.ExtensionContext): string;
16
16
  onAgentSettled(ctx: Pi.ExtensionContext): void;
17
+ onTurnEnd(event: Pi.TurnEndEvent, ctx: Pi.ExtensionContext): Pi.TurnEndEventResult | undefined;
17
18
  onContext(messages: unknown[], ctx: Pi.ExtensionContext): unknown[];
18
19
  onSessionShutdown(reason: string, ctx: Pi.ExtensionContext): void;
19
20
  onSessionStart(ctx: Pi.ExtensionContext): Promise<void>;
@@ -118,6 +118,9 @@ export function createActorExtensionRuntime(pi) {
118
118
  if (!runUiRuntime.flushCompletionBatch(ctx))
119
119
  automaticReview.schedule();
120
120
  },
121
+ onTurnEnd(event, ctx) {
122
+ return runUiRuntime.onTurnEnd(event, ctx);
123
+ },
121
124
  onContext(messages, ctx) {
122
125
  return runUiRuntime.projectContext(messages, ctx);
123
126
  },
@@ -3,9 +3,9 @@
3
3
  * Zones: pi agent sdk boundary, extension host adapters
4
4
  * Owns direct pi SDK imports and exposes narrow pi-actors-facing helpers/types for the composition root.
5
5
  */
6
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
+ import type { ExtensionAPI, ExtensionContext, CustomMessageEntryDraft, TurnEndEvent, TurnEndEventResult } from "@earendil-works/pi-coding-agent";
7
7
  import * as SessionEvidence from "./session-evidence.ts";
8
- export type { ExtensionAPI, ExtensionContext };
8
+ export type { ExtensionAPI, ExtensionContext, TurnEndEvent, TurnEndEventResult };
9
9
  export declare const RUN_COMPLETION_BATCH_CUSTOM_TYPE = "pi-actors-run-batch";
10
10
  export declare const RUN_STEER_CUSTOM_TYPE = "pi-actors-run-steer";
11
11
  export interface PiNotificationSink {
@@ -19,6 +19,7 @@ export interface PiNotificationSink {
19
19
  }
20
20
  export declare function getSessionId(ctx: ExtensionContext): string;
21
21
  export declare function createNotificationSink(pi: ExtensionAPI, ctx: ExtensionContext): PiNotificationSink;
22
+ export declare function createRunCompletionBatchEntry(batchId: string, content: string): CustomMessageEntryDraft;
22
23
  export declare function sendRunCompletionBatch(pi: ExtensionAPI, batchId: string, content: string): void;
23
24
  export declare function sendRunSteer(pi: ExtensionAPI, input: {
24
25
  content: string;
@@ -41,6 +42,10 @@ export declare function dedupeRunSteerContext(messages: unknown[]): {
41
42
  }>;
42
43
  };
43
44
  export declare function removeRunSteerFromContext(messages: unknown[], steerId: string): unknown[];
45
+ export declare function inspectRunCompletionBatchSessionEvidence(ctx: ExtensionContext, input: {
46
+ batchId: string;
47
+ content: string;
48
+ }): SessionEvidence.ActiveSessionEntryEvidence;
44
49
  export declare function inspectRunSteerSessionEvidence(ctx: ExtensionContext, input: {
45
50
  content: string;
46
51
  eventId: string;
@@ -18,8 +18,9 @@ export function createNotificationSink(pi, ctx) {
18
18
  }),
19
19
  };
20
20
  }
21
- export function sendRunCompletionBatch(pi, batchId, content) {
22
- pi.sendMessage({
21
+ export function createRunCompletionBatchEntry(batchId, content) {
22
+ return {
23
+ type: "custom_message",
23
24
  customType: RUN_COMPLETION_BATCH_CUSTOM_TYPE,
24
25
  content,
25
26
  display: true,
@@ -29,10 +30,11 @@ export function sendRunCompletionBatch(pi, batchId, content) {
29
30
  kind: "completion_batch",
30
31
  },
31
32
  },
32
- }, {
33
- deliverAs: "followUp",
34
- triggerTurn: true,
35
- });
33
+ };
34
+ }
35
+ export function sendRunCompletionBatch(pi, batchId, content) {
36
+ const { type: _type, ...message } = createRunCompletionBatchEntry(batchId, content);
37
+ pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
36
38
  }
37
39
  export function sendRunSteer(pi, input) {
38
40
  pi.sendMessage({
@@ -172,6 +174,18 @@ export function dedupeRunSteerContext(messages) {
172
174
  export function removeRunSteerFromContext(messages, steerId) {
173
175
  return messages.filter((message) => steerMessage(message)?.steerId !== steerId);
174
176
  }
177
+ export function inspectRunCompletionBatchSessionEvidence(ctx, input) {
178
+ return SessionEvidence.inspectBoundedActiveSessionEntries({
179
+ getEntry: (id) => ctx.sessionManager.getEntry(id),
180
+ leaf: ctx.sessionManager.getLeafEntry(),
181
+ match: (entry) => {
182
+ const batch = completionBatchMessage(entry);
183
+ if (!batch || batch.batchId !== input.batchId)
184
+ return undefined;
185
+ return batch.content === input.content ? "present" : "conflict";
186
+ },
187
+ });
188
+ }
175
189
  export function inspectRunSteerSessionEvidence(ctx, input) {
176
190
  return SessionEvidence.inspectBoundedActiveSessionEntries({
177
191
  getEntry: (id) => ctx.sessionManager.getEntry(id),
@@ -9,6 +9,7 @@ import * as Pi from "./pi.ts";
9
9
  export interface RunUiRuntime {
10
10
  close(): void;
11
11
  flushCompletionBatch(ctx: Pi.ExtensionContext): boolean;
12
+ onTurnEnd(event: Pi.TurnEndEvent, ctx: Pi.ExtensionContext): Pi.TurnEndEventResult | undefined;
12
13
  projectContext(messages: unknown[], ctx: Pi.ExtensionContext): unknown[];
13
14
  shutdown(eventReason: string, ownerId: string | undefined, ctx?: Pi.ExtensionContext): void;
14
15
  start(ctx: Pi.ExtensionContext, ownerId: string): void;
@@ -23,6 +23,7 @@ export function createRunUiRuntime(deps) {
23
23
  const deliveryObservation = Observability.createRunUiObservationState();
24
24
  const retirementAttempts = new Set();
25
25
  const steerDiagnostics = new Set();
26
+ const completionDiagnostics = new Set();
26
27
  const close = () => {
27
28
  running = false;
28
29
  activeContext = undefined;
@@ -47,6 +48,7 @@ export function createRunUiRuntime(deps) {
47
48
  deliveryTimeout = undefined;
48
49
  recoverQueuedSteers = false;
49
50
  steerDiagnostics.clear();
51
+ completionDiagnostics.clear();
50
52
  if (animationInterval)
51
53
  clearInterval(animationInterval);
52
54
  animationInterval = undefined;
@@ -185,6 +187,57 @@ export function createRunUiRuntime(deps) {
185
187
  });
186
188
  return true;
187
189
  };
190
+ const acceptCompletionBatch = (ownerId, batchId) => {
191
+ const input = { batchId, ownerId, tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir };
192
+ if (!RunDelivery.markRunCompletionBatchQueued(input))
193
+ return false;
194
+ if (!RunDelivery.markRunCompletionBatchPresented(input))
195
+ return false;
196
+ const accepted = journal(ownerId).completion_batch;
197
+ return accepted ? finishPresentedBatch(ownerId, accepted) : false;
198
+ };
199
+ const reconcileCompletionBatch = (ctx, ownerId) => {
200
+ const batch = journal(ownerId).completion_batch;
201
+ if (!batch)
202
+ return false;
203
+ if (batch.phase === "pending") {
204
+ // Returning boundary drafts is not acceptance: prove the exact entry before acknowledging it.
205
+ const evidence = Pi.inspectRunCompletionBatchSessionEvidence(ctx, {
206
+ batchId: batch.batch_id,
207
+ content: RunDelivery.formatRunCompletionBatchMessage(batch),
208
+ });
209
+ if (evidence.status === "absent")
210
+ return false;
211
+ if (evidence.status !== "present") {
212
+ const key = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
213
+ if (!completionDiagnostics.has(key)) {
214
+ completionDiagnostics.add(key);
215
+ ctx.ui.notify(`Actor completion recovery is ${evidence.status}: ${evidence.reason ?? "conflicting session evidence"}. Batch ${batch.batch_id} remains pending.`, "warning");
216
+ }
217
+ return true;
218
+ }
219
+ }
220
+ return !acceptCompletionBatch(ownerId, batch.batch_id);
221
+ };
222
+ const pendingCompletionBatch = (ownerId) => {
223
+ const existing = journal(ownerId).completion_batch;
224
+ if (existing)
225
+ return existing;
226
+ const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
227
+ admitCompletionTransitions(ownerId, snapshot);
228
+ Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
229
+ return journal(ownerId).completion_batch;
230
+ };
231
+ const isCurrentOwner = (ctx) => {
232
+ if (!running || !activeOwnerId)
233
+ return false;
234
+ try {
235
+ return Pi.getSessionId(ctx) === activeOwnerId;
236
+ }
237
+ catch {
238
+ return false;
239
+ }
240
+ };
188
241
  const isIdle = (ctx) => typeof ctx.isIdle !== "function" || ctx.isIdle();
189
242
  const isDeliveryRoot = (ownerId) => {
190
243
  const inheritedOwner = RunDeliveryLineage.getProcessDeliveryOwnerId();
@@ -344,52 +397,20 @@ export function createRunUiRuntime(deps) {
344
397
  });
345
398
  flushCompletionBatch = (ctx) => {
346
399
  const ownerId = activeOwnerId;
347
- if (!running || !ownerId)
400
+ if (!ownerId || !isCurrentOwner(ctx))
348
401
  return false;
349
- try {
350
- if (Pi.getSessionId(ctx) !== ownerId)
351
- return false;
352
- }
353
- catch {
354
- return false;
355
- }
356
402
  if (flushSteers(ctx, ownerId))
357
403
  return true;
358
404
  if (!isDeliveryRoot(ownerId))
359
405
  return false;
360
- let batch = journal(ownerId).completion_batch;
361
- if (!batch) {
362
- const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
363
- admitCompletionTransitions(ownerId, snapshot);
364
- Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
365
- batch = journal(ownerId).completion_batch;
366
- }
406
+ if (reconcileCompletionBatch(ctx, ownerId))
407
+ return true;
408
+ const batch = pendingCompletionBatch(ownerId);
367
409
  if (!batch)
368
410
  return false;
369
- if (batch.phase === "presented") {
370
- finishPresentedBatch(ownerId, batch);
371
- const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
372
- admitCompletionTransitions(ownerId, snapshot);
373
- Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
374
- batch = journal(ownerId).completion_batch;
375
- if (!batch)
376
- return false;
377
- }
378
411
  if (!isIdle(ctx))
379
412
  return true;
380
413
  const content = RunDelivery.formatRunCompletionBatchMessage(batch);
381
- if (batch.phase === "queued") {
382
- if (!RunDelivery.markRunCompletionBatchPresented({
383
- batchId: batch.batch_id,
384
- ownerId,
385
- tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
386
- }))
387
- return true;
388
- const accepted = journal(ownerId).completion_batch;
389
- if (accepted)
390
- finishPresentedBatch(ownerId, accepted);
391
- return flushCompletionBatch(ctx);
392
- }
393
414
  if (!isIdle(ctx))
394
415
  return true;
395
416
  try {
@@ -404,40 +425,33 @@ export function createRunUiRuntime(deps) {
404
425
  });
405
426
  throw error;
406
427
  }
407
- if (!RunDelivery.markRunCompletionBatchQueued({
408
- batchId: batch.batch_id,
409
- ownerId,
410
- tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
411
- })) {
412
- throw new Error("Completion batch changed before queue acknowledgment");
413
- }
414
- if (!RunDelivery.markRunCompletionBatchPresented({
415
- batchId: batch.batch_id,
416
- ownerId,
417
- tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
418
- })) {
428
+ if (!acceptCompletionBatch(ownerId, batch.batch_id)) {
419
429
  throw new Error("Completion batch changed before durable acceptance");
420
430
  }
421
- const accepted = journal(ownerId).completion_batch;
422
- if (accepted)
423
- finishPresentedBatch(ownerId, accepted);
424
431
  ctx.ui.notify(`Actor completions ready: ${batch.members.length}`, "info");
425
432
  return true;
426
433
  };
427
434
  return {
428
435
  close,
429
436
  flushCompletionBatch,
437
+ onTurnEnd(event, ctx) {
438
+ const ownerId = activeOwnerId;
439
+ if (!ownerId || !isCurrentOwner(ctx) || !isDeliveryRoot(ownerId) ||
440
+ event.outcome !== "completed" || isIdle(ctx) || ctx.signal?.aborted ||
441
+ ctx.hasPendingMessages?.() || event.context.pendingMessages.length > 0)
442
+ return undefined;
443
+ if (flushSteers(ctx, ownerId) || reconcileCompletionBatch(ctx, ownerId))
444
+ return undefined;
445
+ const batch = pendingCompletionBatch(ownerId);
446
+ if (!batch)
447
+ return undefined;
448
+ const entry = Pi.createRunCompletionBatchEntry(batch.batch_id, RunDelivery.formatRunCompletionBatchMessage(batch));
449
+ return { entries: [...event.entries, entry], continue: true };
450
+ },
430
451
  projectContext(messages, ctx) {
431
452
  const ownerId = activeOwnerId;
432
- if (!running || !ownerId)
433
- return messages;
434
- try {
435
- if (Pi.getSessionId(ctx) !== ownerId)
436
- return messages;
437
- }
438
- catch {
453
+ if (!ownerId || !isCurrentOwner(ctx))
439
454
  return messages;
440
- }
441
455
  const steerContext = Pi.dedupeRunSteerContext(messages);
442
456
  let contextMessages = steerContext.messages;
443
457
  for (const steer of journal(ownerId).steers) {
@@ -472,21 +486,7 @@ export function createRunUiRuntime(deps) {
472
486
  if (content !== RunDelivery.formatRunCompletionBatchMessage(batch)) {
473
487
  return Pi.removeRunCompletionBatchFromContext(projected.messages, batch.batch_id);
474
488
  }
475
- if (!RunDelivery.markRunCompletionBatchQueued({
476
- batchId: batch.batch_id,
477
- ownerId,
478
- tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
479
- }))
480
- return projected.messages;
481
- if (!RunDelivery.markRunCompletionBatchPresented({
482
- batchId: batch.batch_id,
483
- ownerId,
484
- tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
485
- }))
486
- return projected.messages;
487
- const presented = journal(ownerId).completion_batch;
488
- if (presented)
489
- finishPresentedBatch(ownerId, presented);
489
+ acceptCompletionBatch(ownerId, batch.batch_id);
490
490
  return projected.messages;
491
491
  },
492
492
  shutdown(eventReason, ownerId, ctx) {
@@ -107,7 +107,7 @@ Run = Recipe + Trace + Control
107
107
  ```
108
108
 
109
109
  1. Spawn with the exact logical Recipe identity and caller-owned values.
110
- 2. Retain the returned `run:<id>` and normally wait for the root coordinator's settled completion batch instead of polling. Nested actor completions accumulate under their containing top-level Run and arrive in that one tree-compressed batch; descendant sessions do not need notification options.
110
+ 2. Retain the returned `run:<id>` and normally wait for the root coordinator's completion batch instead of polling. While Pi is active, ready results arrive together at the next safe model-step boundary after the full tool batch; already-idle Pi wakes through a debounced follow-up. Nested actor completions wait for their complete containing top-level tree and arrive in that one compressed batch; descendant sessions do not need notification options.
111
111
  3. Inspect `view=trace` when retained observations or attention matter.
112
112
  4. Inspect `view=control` before diagnosing service readiness, stale work, or saturation.
113
113
  5. Send `message` only for an action declared and consumed by that controlled Recipe.
@@ -14,7 +14,7 @@ A rare Skill Recipe may declare `singleton: true`. Do not pass `as`: the runtime
14
14
 
15
15
  ## Observe
16
16
 
17
- Normally wait for the settled completion batch. Inspect only when requested, when meaningful attention arrives, or when the Run is overdue or blocked:
17
+ Normally wait for the completion batch: ready trees reach an active root coordinator at the next safe model-step boundary after the full tool batch, or wake an already-idle coordinator through a debounced follow-up. Inspect only when requested, when meaningful attention arrives, or when the Run is overdue or blocked:
18
18
 
19
19
  ```text
20
20
  inspect target=run:<id> view=recipe