@sars267/pi-delegation-broker 0.1.0-alpha.0 → 0.1.0-alpha.2

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 (80) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +78 -9
  3. package/SECURITY.md +1 -1
  4. package/docs/EXTERNAL-SUPERVISOR.md +40 -0
  5. package/docs/INTEGRATION.md +74 -4
  6. package/docs/LIVE-VALIDATION-PLAN.md +44 -0
  7. package/docs/PROVIDER-PROXY-PROTOCOL.md +236 -0
  8. package/extensions/child-shim.ts +153 -0
  9. package/extensions/controller-provider-proxy.ts +156 -0
  10. package/extensions/pi-behavioral-enforcement.ts +283 -0
  11. package/extensions/pi-delegation-broker.ts +2622 -0
  12. package/package.json +21 -4
  13. package/src/acceptance-plan.mjs +260 -0
  14. package/src/acceptance-verifier.mjs +173 -0
  15. package/src/anthropic-messages-transport.mjs +603 -0
  16. package/src/apex-admission.mjs +92 -0
  17. package/src/artifact-pipeline.mjs +98 -0
  18. package/src/behavior-monitor.mjs +226 -0
  19. package/src/broker.mjs +1469 -98
  20. package/src/brokered-runner.mjs +925 -0
  21. package/src/capability-compiler.mjs +229 -0
  22. package/src/checkpoint-store.mjs +286 -0
  23. package/src/child-contract.mjs +201 -0
  24. package/src/child-ipc-client.mjs +50 -0
  25. package/src/child-launcher.mjs +440 -0
  26. package/src/child-rpc.mjs +272 -0
  27. package/src/child-runtime-snapshot.mjs +80 -0
  28. package/src/child-skills.mjs +92 -0
  29. package/src/controller-provider-config.mjs +321 -0
  30. package/src/defect-store.mjs +244 -0
  31. package/src/delegation-history.mjs +70 -0
  32. package/src/delegation-inventory.mjs +51 -0
  33. package/src/delegation-job-store.mjs +196 -0
  34. package/src/delegation-policy.mjs +66 -0
  35. package/src/dynamic-provider-watcher.mjs +330 -0
  36. package/src/effect-verification.mjs +130 -0
  37. package/src/evidence.mjs +412 -0
  38. package/src/fake-provider.mjs +19 -1
  39. package/src/fleet-view.mjs +420 -0
  40. package/src/human-approval.mjs +76 -0
  41. package/src/index.mjs +146 -3
  42. package/src/integration-worktree.mjs +286 -0
  43. package/src/ipc.mjs +646 -11
  44. package/src/launcher-attestation.mjs +144 -0
  45. package/src/lossless-json.mjs +153 -0
  46. package/src/model-affinity-journal.mjs +251 -0
  47. package/src/model-preferences.mjs +154 -0
  48. package/src/model-provenance-policy.mjs +134 -0
  49. package/src/model-quality-catalog.mjs +69 -0
  50. package/src/model-selector.mjs +567 -0
  51. package/src/model-thinking-policy.mjs +34 -0
  52. package/src/native-provider-routing.mjs +12 -0
  53. package/src/openai-chat-completions-transport.mjs +318 -0
  54. package/src/parent-wake.mjs +236 -0
  55. package/src/pi-native-provider.ts +769 -0
  56. package/src/proposed-patch.mjs +70 -0
  57. package/src/provider-catalog.mjs +233 -0
  58. package/src/provider-context.mjs +166 -0
  59. package/src/provider-probe.mjs +298 -0
  60. package/src/provider-protocol.mjs +596 -0
  61. package/src/proxy-context.mjs +224 -0
  62. package/src/recursive-admission.mjs +205 -0
  63. package/src/repair-controller.mjs +216 -0
  64. package/src/report-store.mjs +292 -0
  65. package/src/routing-audit-journal.mjs +132 -0
  66. package/src/routing-board.mjs +322 -0
  67. package/src/routing-history-store.mjs +53 -0
  68. package/src/scoped-child-auth.mjs +169 -0
  69. package/src/semaphore.mjs +80 -0
  70. package/src/session-binding-store.mjs +149 -0
  71. package/src/signed-registry.mjs +84 -15
  72. package/src/supervisor.mjs +206 -12
  73. package/src/team.mjs +333 -0
  74. package/src/tool-identity.mjs +7 -0
  75. package/src/trusted-launch-resolver.mjs +445 -17
  76. package/src/unread-notice.mjs +74 -0
  77. package/src/verification-authority.mjs +219 -0
  78. package/src/verified-routing-board.mjs +128 -0
  79. package/src/workflow-scheduler.mjs +492 -0
  80. package/src/worktree.mjs +67 -0
package/CHANGELOG.md CHANGED
@@ -1,7 +1,132 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.2 — 2026-09-05
4
+
5
+ ### Delegation usefulness and evidence
6
+
7
+ - Own the native Pi provider adapter and use only public Pi registry/auth/runtime APIs. No companion extension, private event channel or injected sibling transport is required. A standalone fresh-Pi delegation canary verifies this boundary.
8
+ - Repair gate exceptions now leave durable failure evidence; failed resumption cannot mark a defect resolved. Automatic code repair remains unconfigured.
9
+ - **The parent-owned delegation transport now preserves image-bearing native replay.** Canonical inline image blocks from user and read-tool-result messages remain structured through Pi's native provider implementation. Before dispatch, the exact resolved model must advertise `image` input; text-only models reject the attempt without making a provider call. This keeps credentials in the parent and leaves raw text-only compatibility adapters strict.
10
+ - **Native controller errors before response headers no longer become opaque protocol failures.** SDK error text alone is not provider-health evidence. Until status/headers are observed, even provider-looking `401`/quota text settles as `transport_before_headers`; this avoids the illegal pre-header `auth_fatal` transition that was rewritten to `controller_failure` and then incorrectly poisoned broker capacity.
11
+ - **A Cursor Grok child no longer fails before its first tool runs.** Cursor's OpenAI-compatible adapter composes tool-call identity as `${call_id}\n${item_id}`, joined by one LF rather than the `|` that OpenAI Responses uses. The native controller boundary accepted only the `|` form, so a healthy provider frame was terminated as `malformed_provider_frame (unsupported_tools)` on every attempt and no failover could recover it. Tool-call ids now accept exactly one optional `|`- or LF-joined pair, each half constrained as before and the whole still bounded to 128 characters; tool names, namespaces and route identifiers keep the stricter pattern.
12
+ - **A reasoning-capable Codex child no longer fails on its own reasoning block.** OpenAI Responses streams one reasoning representation and may replace it with a summarized form at `output_item.done`, so requiring the final `thinking_end` content to equal the concatenated deltas rejected a well-formed stream. Reasoning still never crosses the controller boundary and remains bounded by the output cap, but its final value is no longer required to be byte-identical to the streamed deltas.
13
+ - **Malformed-frame terminals now say which frame was rejected.** Block start/delta/end, unsupported stream events and truncated blocks carry distinct fixed reasons instead of one undifferentiated `malformed_provider_frame`, so a live failure names its own cause.
14
+
15
+ - Require an explicit work plan in model-facing tools; retain legacy job compatibility, enforce declared production scopes, and default workflows to two children.
16
+ - Preserve verified history across controllers in an idempotent SQLite ledger; separate model quality, observed availability, consumption and parent-reported usefulness.
17
+ - Remove traffic-volume ranking bonuses, expose confidence, and bound eligible low-risk exploration. Honor selected model ordering at actual lease admission.
18
+ - Add availability/history/feedback tools, parent context, atomic active-task deduplication, and regression coverage for concurrent writers, restart, cancellation and actual lease choice.
19
+
3
20
  All notable changes are documented here. This project follows [Semantic Versioning](https://semver.org/) after `1.0.0`.
4
21
 
22
+ ### Reliability
23
+
24
+ - **Live controllers keep a captured child runtime.** Child extensions and their local source graph are captured when the parent extension loads, materialized in the controller's private directory, and checked before dispatch. Editing the development checkout no longer changes the attested files underneath an active controller. Status exposes the loaded source fingerprint and pending source changes; dependency upgrades still require a process restart.
25
+ - **Recovery and automatic result delivery belong to the originating session.** Startup recovery, durable session bindings, report wakes and default unread collection ignore foreign sessions; idempotency keys are scoped by owner. Legacy jobs without an owner remain available for explicit inspection and are not automatically relaunched. A stale deferred settlement cannot unlock a newer parent turn.
26
+ - **Premium models cannot displace ordinary current routes.** Astra has a separate generation family, Apex is excluded from automatic ordinary routing, and a controller-resolved exact inherited parent selection can retain its premium route. Semantic rescue admission is a controller-only primitive; no automatic live rescue dispatch is enabled by this change.
27
+ - Broker start failures now report the startup cause instead of claiming successful activation.
28
+
29
+ - **Visual delegation now preserves image input through failover instead of terminating at the controller proxy.** The closed provider context admits only bounded canonical `text` and base64 `image` blocks (`png`, `jpeg`, `gif`, `webp`), preserves them through user and read-tool-result replay, and raises the explicit context/wire ceiling to 8 MiB without accepting URLs or unknown media. A vision contract publishes `text,image` on the child proxy; the parent-native adapter independently rejects an image if the exact selected model lacks image input. The older raw OpenAI/Anthropic adapters remain deliberately text-only and reject incompatible context before credential resolution or dispatch. A credentialless real Pi child reads a PNG and delivers the image block to the controller, and a fresh Pi RPC incident canary marks the first vision route unavailable, completes on the second, then proves one terminal wake waits for the busy parent to reach `agent_settled` before collecting the report.
30
+ - **A busy parent can no longer receive a terminal delegation wake while it is already processing.** The extension now treats the full `agent_start`→`agent_settled` interval as non-dispatchable instead of relying on Pi 0.84.2's narrow `isIdle`/`isStreaming` transition. Terminal reports remain pending and coalesce until settlement, then exactly one hidden custom continuation is queued; the fresh visual canary emits no `Agent is already processing a prompt` failure.
31
+ - **A healthy read-only child is no longer killed by a UTF-8 pre-send bound while observed tokens are still under the cap.** `workflow-mtmzxk2i-4-evidence-audit` on `cursor/composer-2.5` reconciled 27 turns to `used=804330` against `cap=1000000` (no `cacheRead` in the usage events), then hard input denied the next `requested=236663` byte reservation before dispatch. The observe contract asked for metered input, but the lease inherited the resource's hard ceiling. Read-only work now keeps `metered_best_effort` on the lease, admits one bounded request while observed usage is below the cap, and reconciles to provider tokens afterward. Effect-capable work stays byte-safe hard. Covered by IPC, a fresh-process worker, a Pi child with the incident numbers, a fresh OS process loading `extensions/pi-delegation-broker.ts`, and a fresh `pi` parent that calls the real `delegate`. Acceptance also includes a bounded live Cursor request whose UTF-8 reservation exceeds its token cap and an exact fresh-parent run seeded at `804330` that admits the next `236663`-byte turn against `1000000`, makes one live Cursor request, returns its marker and settles cleanly. Already-running Pi processes still require a full restart to replace cached ESM; `/reload` is insufficient.
32
+ - **One healthy provider no longer serializes an independent four-node workflow.** Provider/account groups now expose four bounded work slots plus the protected control slot, matching the runner's four execution permits. The prior `maxConcurrent=2` minus `controlReserve=1` policy left exactly one work lease and forced all fallback nodes into `denied_capacity`; observed provider throttles still cool the shared account group.
33
+ - **Workflow status now distinguishes scheduler ownership from live execution.** A durable node marked `running` only means its orchestration promise is unresolved; `delegate_status` now overlays the same volatile attempt state as the fleet widget, so `selecting` / `waiting_capacity` and `route:pending` cannot be misreported as a model actively working.
34
+ - **Controller failures no longer poison provider capacity or churn through accounts.** The controller IPC preserves a bounded protocol reason such as `phase_invalid`, and the runner stops that node without reporting provider unavailability. Provider/account cooldowns now require provider-owned evidence; changing model routes cannot conceal or repair a controller defect.
35
+ - **A provider abort is a dead route, not a dead task.** Cursor/Pi `This operation was aborted` and a child `stopReason` of `aborted` used to classify as `fatal`, so a live `delegate-mtmroplu-2` on `cursor/composer-2.5` ended the job instead of moving to the next healthy route. Those signatures now fail over like other unavailability; an operator `abort()` / disposed runner still stops without hopping accounts.
36
+
37
+ - **Tool-call identity is now one definition instead of five copies, and accepts every compound form providers actually emit.** OpenAI Responses joins `${call_id}|${item_id}`; Cursor's Grok adapter joins the same two components with one LF. The `|`-only rule terminated a healthy Grok tool frame as `malformed_provider_frame (unsupported_tools)` before any tool ran. `src/tool-identity.mjs` is now the single canon imported by context capture, frame validation and the child proxy, so a stale duplicate cannot reject a live provider again; no other control character is admitted.
38
+
39
+ - **Concurrent Pi processes no longer replace one another's broker socket.** Each embedded controller now owns a namespaced socket, lock, and bootstrap token while all controllers continue to share the transactional SQLite capacity ledger. Restarting or shutting down one Pi process can no longer redirect another live process into a foreign controller token and fail every new delegation as `unauthorized`.
40
+ - **Binary read results no longer terminate the next provider turn.** Pi may return a binary file such as `.git/index` through its text-shaped read result. The raw transcript and behavioral digest remain intact, while provider replay now receives a bounded projection with non-printable bytes rendered as explicit Unicode escapes instead of failing with `messages[n].content must be bounded printable text`.
41
+ - **A tool-capable child on a Codex account no longer fails before its first tool call, and the rule that decides it now exists once.** OpenAI's Responses API composes tool-call identity as `${call_id}|${item.id}`, so a live Codex tool call arrives carrying a `|`. Four boundaries validated that field with an identifier pattern that had no place for one, so a child that was asked to read a single file was terminated as `malformed_provider_frame` before any tool ran: the provider frame was well-formed and the route was healthy, but the identity gate refused the shape on every attempt, which is why no failover could recover it. Tool-call identity now accepts exactly one optional `|`-joined pair, each half constrained as before and the whole still bounded to 128 characters, while namespaces, tool names and block ids keep the stricter pattern — this admits the one composition the provider actually emits instead of relaxing validation as a class. The same rule had been copied into five places, and the copy inside the credentialless child proxy was the one that stayed stale after the other four were corrected, so the failure survived a green release check and only reappeared against a live account; the proxy extension now imports the canonical pattern from the context module it already depends on, leaving one definition to keep true. Covered end to end by a brokered child that completes a real `read` tool turn and result replay under a compound id.
42
+
43
+ - **Conservative child input reservations are reconciled after provider usage arrives.** The real proxy used UTF-8 bytes as a fail-closed upper bound, but kept that upper bound forever even when the provider reported the much smaller token count. A multi-step read-only child could therefore hit the controller's cumulative `input_budget_exceeded` cap after valid progress. Successful streams now refund the unused reservation while streams without usage remain fail-closed; controller budget terminals are explicit and are not retried on another provider as if the route were unhealthy. The default cumulative input ceiling is now one million tokens because the pre-send UTF-8 bound is commonly about four times observed usage: the previous 200k default rejected a healthy 127k-token task before its next turn even though its actual request remained within budget.
44
+ - **Prefixed observe-tool aliases no longer spend an unnecessary correction turn.** The behavioral child boundary canonicalizes an allowed `mcp_pi_read`-style alias before automatic declaration/authorization, while preserving the fail-closed allow-list.
45
+ - **Parent wakes no longer claim reports during Pi's end/settle queue race.** A wake now remains unclaimed while Pi is active, settling, or has a queued owner message, then retries only after `agent_settled`. Its `wakeAt` marker is acknowledged only when Pi emits `message_start` for the hidden custom wake, so an abort before consumption remains visible to the owner fallback; durable create-once claims still prevent duplicate dispatch.
46
+ - **Currency probes no longer send controller OAuth tokens to published child proxies.** Numbered Anthropic/Codex loopback aliases are catalog-only for generic probing; parent catalog facts plus exact controller route preflight remain the admission gates, eliminating recurring `slot_proxy_refused` 401 noise without weakening the child proxy's credential boundary.
47
+ - **Broker startup no longer mistakes a recycled PID for a live supervisor.** A stale lock with a valid old start time is reclaimed only when the owner-only broker socket is not listening; recent locks and active listeners remain fail-closed, privileged-PID `EPERM` is handled by the same socket proof, and lock contents are compared before removal.
48
+ - **Admission now uses a fresh provider snapshot and exact route preflight.** Before lease reservation, the controller refreshes the owner-side catalog/health view and verifies the exact model registration, native runtime, current credential resolution and API dialect. Stale routes are quarantined without child launch or physical-attempt spend; controller route failures are failover-eligible, and default workflow attempt budgets leave room for one bounded handoff per node.
49
+ - **Cursor currency no longer probes its synthetic `models.json` URL.** Cursor and numbered Cursor slots use the fresh parent-published catalog as their listing fact, clear only stale resource quarantine when the current OAuth credential is present, and leave capacity breakers untouched; exact native route preflight remains mandatory before lease admission. This prevents an unreachable generic `/models` endpoint from hiding a healthy owner-run Cursor bridge.
50
+ - **Cursor subscription routing is now supported by the controller proxy.** The controller keeps Cursor OAuth in memory; the Pi-native parent boundary invokes the exact registered Cursor provider with retries disabled, forces SSE through the owner-run loopback bridge, and replays approved tool calls/results without child credentials. The older hand-built OpenAI-compatible adapter remains a text-only compatibility seam.
51
+ - **Anthropic subscription OAuth is now supported by the controller proxy.** The controller accepts an exact current OAuth access token, sends Claude Code's Bearer/identity headers, never forwards a refresh token, and the owner can point the live canary at the existing provider entry in `auth.json`. Provider subscription-policy rejections are retained only as coarse, allowlisted classifications.
52
+ - **External supervisor requirements are now explicit.** The package documents the launchd/systemd boundary, attestation-failure recovery and restart/reconciliation obligations; tmux/caffeinate are not represented as crash supervision.
53
+ - **Workflow mutation is now bound to the owner session.** New task/workflow jobs persist the parent session identity; append, close and cancellation operations fail closed from another session, and workflow join acceptance requires a controller verification callback rather than trusting child semantic status.
54
+ - **Integration now requires controller verification authority evidence.** Accepted-proposal semantic flags cannot authorize a worktree merge; proposal and final integration receipts are bound to the patch digest and expected head before any target mutation.
55
+ - **Protected repair approvals are signed and expiry-bound.** Repair proposals persist the approved receipt and revalidate it against the immutable proposal before controller verification, fresh-process canary, reconciliation and resume. The parent extension now exposes controller-owned repair propose/approve/run/status tools; without an owner-injected repair adapter, run remains fail-closed.
56
+ - **The Pi extension now supports owner-injected credentialless proxy startup.** Supplying `ctx.controllerProvider = { providerTransport, routePreflight, routeResolver }` makes the attested `controller-provider-proxy.ts` the child provider and omits scoped child auth; the direct `writeScopedChildAuth` branch remains compatibility-only and explicitly non-live-ready. The native path now carries closed tool schemas, tool-call blocks and tool-result replay while keeping provider credentials and executable functions parent-side. A skipped-by-default live Anthropic child canary exercises the text boundary without putting the key in child state.
57
+ - **Cumulative controller budgets now stop overspend across retries and workflow nodes.** Lease-side input reservations use a conservative UTF-8 upper bound before a real proxy send; output usage is accounted transactionally across repeated streams; logical runner failover sums input/output usage and adopts the controller attempt cap; workflow physical attempts are gated by the root, root output limits are passed down, and output-budgeted teams serialize by policy.
58
+ - **Acceptance execution is now controller-owned.** Delegation and workflow callers can choose only fixed checks (`git-diff-check`, `npm-check`, `npm-test`, `npm-release-check`, or bounded symlink-safe `file-equals`); caller-supplied `argv`, claims and executors are rejected. Checks run with `shell: false`, a sanitized offline environment and bounded output, including proposed-patch verification in the controller scratch worktree.
59
+ - **Provider availability is now time-aware.** The controller rechecks OAuth expiry periodically (including Unix-second and millisecond formats), supports OAuth access tokens in controller-side model probes, applies provider additions/withdrawals incrementally while leases are active, represents an empty authorized fleet instead of retaining expired accounts, and updates broker health from probe failures/recovery. Model-list throttles use bounded half-open unknown probes rather than incorrectly opening the long inference quota breaker; actual inference 429s retain the stronger account cooldown. `Retry-After` is parsed and retained only as a bounded delay.
60
+ - Separate the child contract into independent axes: `thinking` (`auto`, `inherit` from the live parent session, or an explicit level that is never silently lowered), `route` (`auto`, `inherit_model` for the parent's exact current model, and `peer` which fails closed while no calibrated quality-equivalence exists), and an optional schema-versioned `role`. Roles are framing only — quoted inside delimiters, placed before lease rules so the lease speaks last, sanitized against prompt-structure forgery, and unable to grant any tool or authority. Recovered workflow node contracts are re-validated; invalid axes fail closed instead of coercing to defaults. Explicit reviewed `skills` are hashed, capped and passed as `--skill` while `--no-skills` keeps ambient discovery off; a skill cannot grant a tool or effect capability, and a mutated file fails closed against its attested digest. Requested and effective effort are both persisted; the fleet shows `requested→effective` when a provider changed it. Available per task and per workflow node, so one stage can run at peer level while its siblings stay cheap. `inherit_model` is implemented as a selector filter (`requireModelIdentity`) applied beside every existing gate rather than as an override: a named model that is legacy, wrongly provenanced, unhealthy or below the tier's quality floor is still refused, and an unavailable identity denies instead of quietly routing elsewhere.
61
+ - Add a bounded live terminal fleet: footer summary, width-aware below-editor active task/node rows and `/delegation-broker fleet [active|all|id <job>|<job>]` detail show actual safe account/provider/model, requested/effective thinking, retry attempt, elapsed/progress age, token/cache usage and durable-versus-volatile state without prompts, child prose, terminal controls or credentials. Runner events drive positive progress; silence is unknown; recovered attempts render lost/reconciling; terminal reports retain safe route/effort/usage projections.
62
+ - Make cancellation reach work that owns no child process yet: `abort(id)` now matches the logical run and its failover attempts, aborts a pending admission and stops a run parked in a capacity wait, and `dispose()` aborts admissions, clears volatile attempts and refuses later launches.
63
+ - Close durability races exposed by the fleet: terminal reports reconcile nonterminal task/node projections (including pre-upgrade node reports without `logicalId`) before restart can replay them, cancellation or deadline expiry that races a completed child preserves the paid-for artifact instead of discarding it, durable workflow cancellation cannot be overwritten by stale scheduler state, and a create-once sent marker prevents stale report JSON from resurrecting wake delivery.
64
+ - Add Slice 3 recovery primitives: the controller records structured immutable defect observations from tool/controller boundaries; children can publish bounded partial checkpoints through lease-scoped IPC; only controller schema/provenance-accepted checkpoints are persisted into a replacement prompt, conflicting partials are withheld, and original artifacts remain immutable.
65
+ - Bind durable delegation roots to the parent Pi session and compaction cursor. Before each agent start the extension injects only bounded controller state (root ids/statuses/node counts), never child prose; session restart and `session_compact` re-establish the binding instead of treating an in-memory parent context as the recovery source.
66
+
67
+ - **Background delegation now wakes the parent automatically.** A terminal top-level task/workflow queues a typed hidden custom `followUp` with `triggerTurn: true`, so an idle parent collects the durable report and continues instead of waiting for another owner message. It never calls `sendUserMessage`; because Pi maps custom messages to provider-visible user role, the always-on `delegate_collect` prompt guideline provides persistent controller-state system framing and the payload carries only bounded IDs/statuses, never child text. Persistent create-once pre-send claim, sent and read markers prevent ambiguous crash replay, duplicate model spend and collection/delivery resurrection. Claim paths are never automatically released, stale-reaped or pruned; terminal bursts coalesce, definite unclaimed pending wakes recover after restart, legacy unread reports do not replay, and system-prompt inbox notice remains the failure/ambiguity fallback.
68
+
69
+ - **Read-only children no longer spend a model turn declaring every observation.** The child guard now declares and authorizes the closed `read`/`grep`/`ls`/`find`/`test` set automatically, while effect tools still require the model's exact prior `broker_declare_action`. This removes repeated declaration/read protocol loops that exhausted Cursor child turns and produced `incomplete` failovers. Native Cursor fresh-process observe and isolated effect canaries pass; the latter is bounded evidence, not standing production approval.
70
+
71
+ - **Live delegation recovers and drains.** Unknown routes get bounded probes, busy accounts queue without spending attempts, incomplete tool turns are not success, and settled parents reap orphan RPC children. Live canaries: `LIVE2_OK`, `UNKNOWN_LIVE_OK`, `CAPACITY_PROTOCOL_OK`.
72
+
73
+ - **`/delegation-broker stop` is a real deactivation now.** The command used to only set a flag: the broker process kept running, held the lock and socket, and auto-started again on the next session regardless. `stop` now shuts the supervisor down (lock and socket released), and a stopped broker no longer auto-starts on `session_start`. `start` activates it again.
74
+
75
+ - **A declaration written as a JSON string no longer blocks the invocation.** Cursor's `claude-4.6-opus-*` children declared `read` with args as a JSON-encoded string, then invoked it with the object form. The behavioral monitor hashed both literally, called the invocation "differs from declared action", blocked every tool, and all four Cursor routes settled with empty text. `requireAction` now parses a stringified-JSON args payload before hashing; a string that is not JSON stays verbatim. Controller-owned `propose_patch` hashing also canonicalizes a missing terminal Git-diff newline, matching the child shim's application normalization without relaxing other action arguments.
76
+
77
+ - **A heartbeat no longer leaves the child unauthorized.** `heartbeat` used to extend only `leases.expires_at`. The IPC token copies expiry at issue time, and `leaseForCapability` requires **both** rows to be live. Live canary `workflow-mt166q44-1-pkg-name` heartbeated the Cursor lease and still got `unauthorized` 655ms after the original 30s snapshot, then settled with empty text which the runner treated as `completed`. Heartbeat now updates `lease_capabilities.expires_at` too. An observe child that settles with no answer is `unavailable` and fails over. `mcp_pi_read` declared for allowed `read` is the same tool.
78
+
79
+ - **A teammate no longer dies because the first child took the only Cursor slot.** Each account admits one work child (`maxConcurrent` 2 minus a control reserve). Two parallel `delegate_workflow` nodes both selected Cursor, the second got `denied_capacity`, and the resolver treated that as "no compatible broker capacity" — even though Kimi and Codex were idle. A full allow-list now excludes those resources and selects again. Separately, the workflow scheduler was dropping `tier` and `capabilities`, so every node looked like a cheap read and collapsed onto that one slot.
80
+
81
+ - **Cursor (and other proxy-backed OAuth) no longer crash the parent Pi session.** Isolated children run `--no-extensions`, so Pi cannot use a copied Cursor `type: oauth` entry and threw `No API key found for cursor`. That RPC error was an unhandledRejection that Node promoted to `uncaughtException`, which exited the interactive session. `writeScopedChildAuth` now materializes a proxy-backed OAuth access token as `type: api_key` (the local Cursor proxy reads Authorization and uses that token as the leased account). Native Anthropic/Codex OAuth is still copied verbatim. Child RPC request promises keep a rejection sink so a late error cannot kill the parent, and `No API key found` classifies as `auth_fatal` so failover continues if it still happens.
82
+
83
+ - **The watcher now reads the real model list from `models.json` for auth-only providers.** A provider present in `auth.json` with endpoint config but no `models-store.json` entry — exactly what pi-multi-account provisions for its Cursor and Kimi rotation slots — used to register a single placeholder model named `default`, hiding every real model from routing. When `models.json` carries a model list, those models (string or object form) are registered instead; the placeholder remains only when no list exists.
84
+
85
+ - **Unread background reports no longer steal the parent turn.** `before_agent_start` used to inject the inbox as a `custom` message (`display: false`). Pi converts custom → user and appends it after the actual prompt, so the last user message — the one the model treats as the current task — became "read these reports". That collided with pi-multi-account: failover continuation is itself a `sendUserMessage` follow-up, and a process restart re-blasted unread files that are never pruned (including failed probes from the previous day). The notice now goes through `systemPrompt`, existing unread ids are seeded at session start, and a failover continuation turn is skipped so the continuation prompt stays the task.
86
+ - **Breaking:** `catalogToBrokerRegistry` now maps a catalog by what actually constrains delegation. A resource is one `(provider, model)` pair rather than one provider represented by its strongest model, so a provider's cheap models are selectable instead of invisible; a capacity group is the account, because every model of one account draws on one quota, so a rate limit cools all of them; and a profile is a capability tier shared across providers (`caps/<sorted.capabilities>/v1`) rather than a per-provider model identity, which is what allows a contract pinned to a class to be served by whichever provider is currently alive. `maxConcurrentPerProvider` now defaults to 2, since a single-slot group reserves its only slot for control admission and can never admit work. Model ids may carry vendor paths, `:variant` suffixes and `~rolling` aliases, and a provider may publish up to 4096 models — a real aggregator account publishes ~350, and the previous 256 cap made `DynamicProviderWatcher.refresh()` throw while `start()` discarded the error, so the dynamic registry silently never loaded.
87
+ - Added `model-selector.mjs`: controller-owned capability-aware selection. `selectModelForTask` derives a task's required capabilities, picks the weakest sufficient capability tier that has a usable resource right now, and returns a contract the broker accepts plus the ranked alternatives. Two asymmetric rules govern it: downward substitution is forbidden even when a weaker class is all that remains, and upward substitution is required when the cheapest sufficient tier has nothing alive, so delegated work finishes instead of being denied for the sake of a saving. Resources rank by inventory confidence before cost, and `allowedProviders` / `excludeResources` confine which accounts delegation may spend. `createSelectContract` builds the launch resolver's callback from a registry read per launch, so a long-lived resolver does not route against a provider set captured at construction.
88
+ - Added `SqliteLeaseBroker#updateRegistry`: an incremental registry update that applies while work is in flight. `reloadRegistry` replaces the whole registry and therefore refuses whenever any lease or task is active — under continuous delegation that moment never arrives and a newly authenticated account would never become usable. Additions now apply immediately; a withdrawn resource that still carries a live lease is retired (takes no new work, deleted once its last lease ends) instead of being deleted underneath it; a withdrawn resource that reappears mid-drain is restored; and a policy change to a live capacity group is reported as deferred rather than silently applied over existing admission accounting. Added `SqliteLeaseBroker#inventory` so a controller can see live health, retirement, cooldown and breaker state — a registry snapshot describes the shape of the world, not which parts of it are answering.
89
+ - `BrokeredChildRunner` now finishes a delegated task across a changing provider set. `model` is optional: without it the controller's selector chooses, and the child launches on the model its lease is actually accounted against (`resolvedModel`) rather than the one merely predicted, since a contract pins a class and the broker may lease a different resource in it. `run()` classifies a terminal child failure as `rate_limited`, `auth_fatal`, `unavailable`, `context_exhausted` or `fatal`, reports provider health to the broker, excludes that resource and retries elsewhere within a bounded attempt count; a context overflow re-routes to a larger-context class without marking the healthy provider down, and a genuine task failure is not retried. `BrokeredLaunchResolver` gained `reportProviderRateLimited` / `reportProviderUnavailable` — a throttled account still appears connected in Pi's own files, so a failed attempt is the only way staleness can be observed at all — and an injectable `spawnChild` seam so the failover path is testable without a real Pi process.
90
+ - `DynamicProviderWatcher.refresh()` now reports `failed` with a reason instead of throwing into a discarded promise, fingerprints model ids as well as provider names (a provider gaining or losing a model previously reported `unchanged` and never reloaded), and does not commit a fingerprint for a catalog it could not represent or a registry update that was deferred.
91
+
92
+ - Added `ControllerAcceptanceVerifier`: a fixed controller-owned, serial command/test verifier plan with injected read-only runner, per-check timeout/abort, retained controller evidence and standard acceptance validation. It exposes no worker-selected command, expected output or evidence descriptor; malformed, failed, timed-out, runner-error and overlapping verification attempts reject. `ControllerVerificationAuthority` now retains a receipt bound to the exact task/lease/fence, and `finalizeVerifiedTask` rejects an otherwise well-formed unattested/replayed receipt. `ControllerQueuedTaskVerifier` provides the controller-only queued child-close → fixed verification → terminal-finalization lifecycle.
93
+ - Added a durable controller-owned effective-capability binding for each lease. `BrokeredLaunchResolver` binds the compiled capability before passing a lease-scoped token; children can retrieve only that record, while controller IPC centrally performs behavioral action declaration, blocking authorization and completed-result observation with controller-derived state digests. Behavioral audit events are separate from provider telemetry/health. Added a source-auditable Pi `tool_call`/`tool_result` reference extension that requires a one-action declaration and lease heartbeats.
94
+ - Added controller-pinned launcher attestation for the source-only `pi-subagent-workflow` seam: each explicit extension has a reviewed SHA-256, the behavioral extension must be final, bytes are rehashed before spawn, and `broker_declare_action` must appear in the child startup report before handoff. `BrokeredLaunchResolver` rejects an effect-capable child before reservation if its pinned attestation configuration is absent. The seam remains unaccepted; effects stay unavailable until upstream/reviewer acceptance and final-gate verification.
95
+ - Added `captureProviderContext`: one shared closed, lossless child→controller context vocabulary for fake and real transports. It allows bounded canonical text/image input blocks, text/tool-call assistant messages, matching text/image tool results, and named JSON-schema tools; provider options, response IDs, replay/cache state, unknown modalities and unknown fields fail before route or credential resolution.
96
+ - Added a low-level `AnthropicMessagesTransport` as the first real-provider transport primitive and wired it through the same controller IPC frame/settlement boundary as the fake transport. `ControllerCredentialStore`, owner-only credential-free `ControllerRouteTable`, and expiring/bounded `ControllerLiveProviderApproval` construct its one exact controller route; it has no environment/keychain/OAuth fallback, account rotation, retry, redirect or automatic failover. IPC accepts the real transport only with its paired route resolver, never alongside fake transport. It is mock-tested only and has made no live provider call.
97
+ - Integrated the capability compiler into the launch resolver: `BrokeredLaunchResolver` now creates an `EffectiveChildCapability` from the contract and lease, compiles it into `promptRules` (child-visible) and `authorizationPolicy` (controller-enforced), and includes both in the returned policy. A `deriveAllowedTools` helper maps operation classes to default tool sets.
98
+ - Added unified capability compiler: `createEffectiveChildCapability` validates and deep-freezes one immutable child-facing capability (operation class, allowed tools, doneWhen, budget, behavioral enforcement); `compileEffectiveChildCapability` generates prompt-visible rules and executable authorization from that single source. `BehavioralRunMonitor` now optionally accepts an authorization policy that denies tools outside the allowed set (terminal `tool_not_allowed`) and effect-capable operations without a wired blocking monitor (terminal `behavioral_enforcement_unavailable`).
99
+ - Added `RoutingBoard`: dynamic, continuously updated scoreboard tracking machine-verifiable outcomes per (resource, capability) pair. Uses Wilson score confidence intervals for routing scores (small samples don't outrank large), exploration bonuses for under-observed resources, drift detection via rolling-window divergence, consecutive-failure exclusion, recency decay, and export/import for persistence. The one-provider degenerate case reduces to monitoring. `ControllerVerifiedRoutingBoard` now records only receipt-bound controller verifier outcomes after broker terminal finalization and can reorder only the exact signed resource candidate permutation; model-graded, child, and provider self-reports are rejected.
100
+ - Added `provider-catalog.mjs`: converts a Pi modelRegistry snapshot (populated by pi-multi-account or other extensions) into a broker registry. Each provider becomes a resource in its own capacity group; each provider's strongest model becomes a profile with derived capability supports. The broker consumes a plain catalog snapshot — it does not know about multi-account as a package.
101
+ - Added transactional registry reload: `reloadRegistry` validates the candidate, denies reload while active leases or pending tasks exist, atomically deletes and re-inserts the registry in a single transaction with SQLite rollback on failure.
102
+ - Added `ArtifactPipeline` with bounded pending queue, byte cap, ordered drain, terminal durability barrier, and re-attemptable failure. The streaming path calls `pipeline.barrier()` before `settlement.markPersisted()`; if the barrier fails, the crash-repair class stays `terminal_unpersisted` instead of treating the attempt as durably settled.
103
+ - Wired framed streaming into the IPC layer: children can now send bounded typed context (systemPrompt, messages, tools, options) via the new `providerStream` method, which validates context through `captureLosslessJson`, creates an immutable `AttemptRouteSnapshot`, streams frames back through `ProviderStreamAssembler`-validated grammar, and settles through `AttemptSettlement`. The legacy digest-only `providerAttempt` remains for backward compatibility. A `streamProviderIpc` client reads NDJSON frames until the validated terminal. Real transport wiring requires the separately described exact-route, mock-tested transport path; credentials are never included.
104
+ - Implemented deterministic provider proxy protocol machinery: immutable `AttemptRouteSnapshot` with one-time route/credential fingerprinting, versioned framed envelope validation, strict stream grammar (block lifecycle, tool-call identity pinning, cumulative usage monotonicity), exactly-once terminal CAS, attempt phase machine with crash-repair classification, and closed terminal/outcome vocabulary with provenance and retry-eligibility properties. No transport, credential, or IPC wiring is included; live use remains prohibited.
105
+ - Added the versioned controller-owned real-provider proxy design gate, including strict stream grammar, immutable route/account binding, terminal/outcome taxonomy, cancellation, backpressure and adversarial acceptance tests; implementation remains absent and live use remains prohibited.
106
+ - Behavioral action/result hashing now uses one bounded detached lossless-JSON snapshot; lossy numbers, sparse/decorated arrays, cycles, accessors and exotic containers fail closed instead of colliding or changing after declaration.
107
+ - Added exact ready-task claim, pre-handoff requeue and `awaiting_result` reconciliation; later controller verifier receipt finalization supersedes terminal parent-result completion.
108
+ - Added clamped queue aging, protected control/verifier dispatch-window representatives and per-class p95/max wait metrics so neither strict priority nor an aged backlog hides a class.
109
+ - Effect-capable contracts now fail closed with `behavioral_enforcement_unavailable` until a trusted launch path asserts a wired blocking monitor; the enforcement capability is carried on admitted leases.
110
+ - Added command/file/test/URL semantic recapture projections and validator integration instead of byte-exact spot audit; volatile normalization is opt-in and conflicts fail closed when acceptance names the stripped field.
111
+ - Backward clock steps now fail closed against the durable ledger time floor rather than extending capabilities.
112
+ - Added contract `doneWhen`, prompt-digest binding and positive latency-budget admission checks.
113
+ - Added deterministic behavioral monitoring for no-progress, typed action mismatch, declared-change reconciliation and completion claims.
114
+ - Replaced digest-only evidence indexing with redacted content-addressed retention, restart verification, corruption detection and explicit pruning.
115
+ - Added durable task waiting with explicit recovery owner/deadline and supervisor dispatch.
116
+ - Added `control | verify | work` admission reserves to prevent work fan-out from starving merger/verifier capacity.
117
+ - Added confidence-labelled schema-v2 inventory enforcement, registry-defined cooldowns and a one-probe half-open breaker.
118
+ - Registry/persisted-policy mismatches now fail closed instead of silently retaining stale capacity or admission rules.
119
+ - Added controller-rooted evidence validation; self-reported evidence cannot satisfy acceptance.
120
+ - Queue size is hard-bounded; high-risk contracts must declare all three hard budget dimensions and positive caps.
121
+ - A success from an older in-flight lease cannot close a breaker opened by a concurrent 429.
122
+ - Expanded deterministic liveness, priority, cooldown and evidence tests.
123
+
124
+ ## 0.1.0-alpha.1 - 2026-08-15
125
+
126
+ OIDC provenance validation release. No runtime changes from `0.1.0-alpha.0`.
127
+
128
+ - Published through the verified GitHub Actions trusted publisher with provenance.
129
+
5
130
  ## 0.1.0-alpha.0 - 2026-08-15
6
131
 
7
132
  Initial public research release.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @sars267/pi-delegation-broker
2
2
 
3
- > **Alpha / research reference — do not connect it to a real provider account or credential.**
3
+ > **Alpha / research reference — a real provider request requires a separately authorized, pre-registered live experiment.**
4
4
 
5
5
  A single-host Node.js reference implementation for capability-scoped Pi child delegation. It demonstrates a safe control path:
6
6
 
@@ -8,28 +8,59 @@ A single-host Node.js reference implementation for capability-scoped Pi child de
8
8
  trusted controller → signed registry → SQLite lease broker → scoped Unix-socket capability → isolated child policy
9
9
  ```
10
10
 
11
- It is designed to fail closed when a provider transport is not explicitly supplied. The package ships **no** HTTP client, OAuth integration, credential store, account rotation, real model proxy, or automatic provider failover.
11
+ The Pi extension owns its native provider adapter and uses Agent Pi's model registry, provider runtime and authentication APIs directly. It keeps credentials in the parent while children receive a scoped IPC route. No other extension is required to initialize this path. The low-level library additionally supports explicitly injected transports; a missing transport fails closed.
12
12
 
13
13
  ## Status
14
14
 
15
15
  This package is publishable as an **`alpha` research artifact**, not a production broker. Its API, file schema and lifecycle hooks may change before `1.0.0`.
16
16
 
17
- Validated locally on Node 26 with deterministic fake transport only. The fake transport is opt-in from `@sars267/pi-delegation-broker/testing` and must never be wired to a real model route.
17
+ Validated locally on Node 26 with deterministic transports, native-provider tests, and owner-authorized fresh-process Cursor canaries. The native canary completed a credentialless child read and isolated `propose_patch`/controller `npm-test` flow on `cursor/claude-4-sonnet`; the child had no `auth.json` and the broker had no leaked lease. These bounded checks are evidence for the path, not unattended or general production approval. The fake transport is opt-in from `@sars267/pi-delegation-broker/testing` and must never be wired to a real model route.
18
+
19
+ ## Delegation decisions and evidence
20
+
21
+ New delegations declare the child's result and the parent's independent work in `work`. Small tasks stay local; default workflow concurrency is two and planned children default to three physical route attempts. `delegate_inventory` supplies advisory availability before choosing, while launch performs the authoritative readiness/lease checks. `delegate_feedback` records whether the parent actually used the result. `/delegation-broker history` shows shared route and verified performance history across restarts.
22
+
23
+ Quality, availability, evidence count and consumption are separate. Unknown models are not ranked below proven failures merely for lacking observations. The bounded exploration policy only considers currently eligible, independently checked low-risk tasks. See the [product contract](docs/INTEGRATION.md#delegation-product-contract-2026-09-05) for ownership, cancellation, storage, fairness and measurement limits.
18
24
 
19
25
  ## What it provides
20
26
 
21
27
  - SQLite (`BEGIN IMMEDIATE` + WAL) lease reservation, TTL, fencing and shared capacity groups.
28
+ - Durable bounded task waiting with named recovery owner/deadline and supervisor wake-up after capacity changes.
29
+ - `control | verify | work` admission classes with clamped queue aging and protected dispatch-window representatives, preventing either old work or fresh control from disappearing behind backlog.
30
+ - Contract admission requires child-facing `doneWhen`, exact prompt-digest binding and a positive latency budget. Output usage is hard-capped cumulatively across provider streams and failover. Read-only input is metered: the controller admits one bounded UTF-8 reservation while observed tokens are below the cap, then reconciles to provider usage; effect-capable input stays byte-safe hard. Workflow roots gate physical attempts and pass remaining output budget into each node, with output-budgeted teams serialized to preserve the cap.
31
+ - Workflow mutations are bound to the originating parent session, and team joins accept only controller-verifier-bound node receipts. Integration requires authority receipts bound to the exact patch digest and target head; protected repairs require signed, expiry-bound owner approval. The extension exposes controller-only repair lifecycle tools, but repair execution stays fail-closed until an owner-injected verification/canary/reconciliation adapter exists.
32
+ - A controller-owned behavioral monitor for typed action reconciliation and no-progress detection. The lease durably binds one effective capability; lease-scoped IPC retrieves it and centrally gates declaration, authorization and completed-tool observation. An effect-capable child receives only a restricted `propose_patch` tool, which applies a validated Git diff only to a disposable worktree; the controller re-applies and checks it in a separate scratch worktree before returning an unapplied patch. Attestation, worktree isolation and controller acceptance checks are mandatory.
33
+ - Registry-defined inventory confidence, pessimistic cooldown policy and one half-open probe per group. The dynamic controller rechecks credential expiry and provider listings periodically, supports OAuth subscriptions in controller preflight, applies account additions/withdrawals while work is active, and represents an empty authorized fleet explicitly. Recent credential-free provider listings persist across restart with a bounded TTL; an owner-only routing audit journal records route/failover/legacy transitions and observed token efficiency without prompts, credentials or price data.
34
+ - Capability-aware model selection over a provider set that changes while work runs. A catalog maps to one resource per `(provider, model)`, one capacity group per account and one profile per capability tier shared across providers. Before ranking, production extension routing applies hard provenance gates: automatic routes must be current and belong to a native first-party/mixed-subscription billing pool; Cursor's own Grok/Composer pool is distinct from its third-party catalog; OpenRouter/Ollama/opencode aggregators are default-deny and require an exact user tier allowlist. Previous/deprecated/unknown generations never enter autonomous routing, and an unavailable explicit pool denies rather than silently broadening to the catalog. Receipt-backed learning can reorder only the already eligible set.
35
+ - Parent-free durable workflow jobs. `delegate_workflow` submits atomically and returns a workflow id by default; `delegate_status`, `delegate_list`, `delegate_collect` and `delegate_cancel` operate on the durable lifecycle. Read-only workflows recover after controller restart, cancellation is idempotent, dependency reports are passed only through explicit `inputs`, and submission acknowledgement, no-progress watchdog, absolute attempt ceiling and whole-workflow deadline are separate controls.
36
+ - Automatic parent wake after a top-level background task/workflow settles. The controller emits a typed hidden custom `followUp` with `triggerTurn: true` (never `sendUserMessage`) only after the full active parent interval reaches `agent_settled`, so a busy parent is never prompted recursively; an idle parent resumes, collects the durable report and continues the owner task. Pi currently converts custom messages to provider-visible user role; the always-on `delegate_collect` system guideline therefore frames the marker as controller state, and the wake contains only bounded task IDs/statuses—never child text or owner-like instructions. Wake dispatch uses persistent create-once pre-send claim, sent and read markers, coalesces bursts and deduplicates per live session. Claim pathnames are never automatically released, stale-reaped or pruned: any host/crash ambiguity falls back to the next genuine owner turn rather than spending twice, collected state cannot be resurrected by stale JSON, definitely unclaimed pending wakes remain restart-recoverable, and legacy inboxes do not replay.
37
+ - A controller-driven terminal fleet view. A compact below-editor widget and footer status show active task/node state, actual safe account/provider/model, effective thinking, retry attempt, elapsed time, last progress age and token/cache usage. `/delegation-broker fleet [active|all|id]` drills into durable job/report versus volatile attempt state. Recovery is labeled `lost/reconciling`; silence is never inferred healthy. The view is bounded and never includes credentials, prompts or child report prose.
38
+ - Independent child contract axes. `tier` sets the quality floor, `thinking` sets reasoning effort (`auto`, `inherit` from the live parent session, or an explicit `off…max` level that is never silently lowered), and `route` chooses identity: `auto` lets the selector decide, `inherit_model` asks for the parent's exact current model when a genuine peer is needed, and `peer` fails closed while no calibrated quality-equivalence exists rather than guessing. An optional schema-versioned `role` (name, mission, deliverables, boundaries) is quoted inside delimiters and placed before lease rules, so the lease remains the last authoritative voice: it shapes how a child works and never grants a tool, permission or effect capability. Recovered node contracts are re-validated and fail closed on invalid axes. Reviewed `skills` are explicit absolute paths with content digests: ambient discovery stays off, `--skill` is the only injection, and a skill never grants tools. `inherit_model` narrows the candidate set through the selector's `requireModelIdentity` filter, applied beside the currency, provenance, quality-floor and health gates rather than instead of them, so naming a model can only ever reduce what is eligible; an inadmissible identity denies. Requested and effective effort are both persisted, and the fleet shows `requested→effective` whenever a provider changed it.
39
+ - Slice 3 recovery: controller/tool defects are captured as bounded immutable records with provenance; a child may publish partial structured work through lease-scoped `broker_checkpoint`, but only controller-accepted, conflict-free checkpoints are offered to a replacement attempt. Checkpoints are partial data, never completion or authority. Durable parent-session bindings retain active roots and the Pi compaction cursor; each new agent turn sees only bounded controller status, not child report prose.
40
+ - Persistent content-addressed, redacted controller evidence with command/file/test/URL semantic recapture comparators and explicit retention/pruning; self-reported evidence cannot satisfy acceptance. `ControllerAcceptanceVerifier` runs a fixed controller-owned command/test plan serially; `ControllerVerificationAuthority` retains a receipt bound to exact task/lease/fence, and `ControllerQueuedTaskVerifier` alone connects close→verify→terminal finalization.
22
41
  - SHA-256-hashed lease capabilities for owner-only Unix-socket IPC.
23
42
  - Signed Ed25519 capability-registry verification before supervisor startup.
24
- - Owner-only state directory, lock, socket, bounded shutdown and periodic TTL sweep.
43
+ - Framed streaming IPC: children send bounded closed-schema context via `providerStream`; a native Pi `broker-proxy` provider can use that lease-scoped IPC route with **no upstream provider credential or child `auth.json`**. The native controller boundary replays approved JSON-schema tools, assistant tool calls, and bounded canonical text/image user or tool-result blocks; executable functions, provider options, remote image URLs, unknown media and reasoning blocks never cross it. Vision input is advertised to the child only for a `vision_input` contract, and the exact native model is checked again before dispatch. `captureProviderContext` validates one closed conversation and takes one `captureLosslessJson` snapshot before route/credential resolution. The controller creates an immutable `AttemptRouteSnapshot`, streams `ProviderStreamAssembler`-validated frames, and settles through `AttemptSettlement` with an `ArtifactPipeline` terminal durability barrier. A `streamProviderIpc` client reads NDJSON frames until the validated terminal. The legacy digest-only `providerAttempt` remains for backward compatibility. IPC requires either the deterministic fake transport or an explicitly injected `providerTransport + routePreflight + routeResolver` pair, never both; neither credentials nor a live route are packaged. The older hand-built OpenAI/Anthropic transport seams remain text-only; use the Pi-native pair for tool-capable work.
44
+ - Independently tested low-level `AnthropicMessagesTransport` and `OpenAIChatCompletionsTransport` controller-only route gates: exact injected endpoint/credential resolution (API key or current subscription OAuth access token), one raw dispatch, retry disabled by construction, bounded strict SSE normalization, no environment/keychain/alternate-account fallback, one route-table generation and an expiring request approval. The OpenAI-compatible path permits loopback HTTP only for an owner-run adapter such as the Cursor subscription bridge. This is not live-provider evidence.
45
+ - Unified capability compiler: `createEffectiveChildCapability` + `compileEffectiveChildCapability` generate prompt-visible rules and executable authorization from one immutable source. `BrokeredLaunchResolver` compiles and durably binds the capability to its lease before exposing the lease-scoped IPC token; a child can retrieve only that bound record. `BehavioralRunMonitor` runs centrally behind IPC (tool-not-allowed and behavioral-enforcement-unavailable terminal denials).
46
+ - Owner-only state directory, bounded shutdown and periodic TTL sweep. Concurrent parent Pi processes share one transactional SQLite capacity ledger, but each controller incarnation owns a namespaced lock/socket/token and private evidence journals; one Pi reload or shutdown cannot replace another controller's IPC route.
25
47
  - A controller-only resolver that checks the exact approved model before creating a child policy.
26
48
  - Pre-handoff and post-session cleanup hooks for a compatible patched child launcher.
27
49
  - Deterministic test fixtures for success, 429, expiry, cancellation and stale capability scenarios.
28
50
 
51
+ ## Optional companion
52
+
53
+ [`pi-multi-account`](https://github.com/Sarrius/pi-multi-account) adds interactive account discovery, rotation and recovery to Agent Pi. Both extensions work independently; neither imports, initializes, or requires the other. Installed together, they use the same public Pi provider/model catalog, while keeping their own task and retry responsibilities. The broker can use any provider correctly registered in Pi, regardless of how it was registered.
54
+
55
+ ## Help improve reliability
56
+
57
+ A reproducible [issue](https://github.com/Sarrius/pi-delegation-broker/issues), focused PR, regression test or clearer documentation can help everyone. Share Pi/extension versions and expected versus actual behavior, with credentials and private task data removed. Different real workflows reveal failures one maintainer cannot anticipate alone.
58
+
29
59
  ## What it deliberately does **not** provide
30
60
 
31
- - A real provider proxy, streaming upstream API calls, credential vault or account discovery.
32
- - Multi-host coordination, automatic stale-lock recovery, production daemon/service management or dashboard.
61
+ - A durable secret vault/rotation, unattended live-provider approval, or provider failover outside the broker's explicit leased Pi-child path. The library has a controller-owned proxy child path, but enabling it against a real provider still requires an explicit approved route/credential injection.
62
+ - Multi-host coordination, automatic stale-lock recovery, production daemon/service management or dashboard. See [`docs/EXTERNAL-SUPERVISOR.md`](docs/EXTERNAL-SUPERVISOR.md) for the required host-owned crash/reboot boundary; `tmux` and `caffeinate` are not substitutes.
63
+ - A complete controller scheduler that turns every ready queued lease into a child launch. The reference exposes durable readiness; integration remains controller-owned.
33
64
  - Signing-key storage, rotation, revocation or registry distribution.
34
65
  - A stable upstream Pi launcher API. The [integration patch](https://github.com/Sarrius/pi-delegation-broker/tree/main/patches) is source-repository-only and is **not** installed by this package.
35
66
  - A sandbox. Worktrees and a minimal environment are not operating-system isolation.
@@ -48,7 +79,39 @@ The first public prerelease should use the `next` npm tag:
48
79
  npm install @sars267/pi-delegation-broker@next
49
80
  ```
50
81
 
51
- Do not install from npm until the repository release checklist is complete and the package name is confirmed available.
82
+ The published prerelease is a research artifact only; installing it does not authorize connection to any real provider/account.
83
+
84
+ ## Pi extension controls
85
+
86
+ The parent extension exposes controller-owned policy controls; they edit only `~/.pi/agent/delegation-broker/preferences.json` through schema validation, never a child prompt:
87
+
88
+ ```text
89
+ /delegation-broker status
90
+ /delegation-broker fleet
91
+ /delegation-broker fleet all
92
+ /delegation-broker fleet <id>
93
+ /delegation-broker fleet id all # disambiguate a job literally named "all"
94
+ /delegation-broker models
95
+ /delegation-broker models openrouter
96
+ /delegation-broker tier frontier list
97
+ /delegation-broker tier standard add glm-5.3 zai opencode-go-api
98
+ /delegation-broker tier standard remove glm-5.3 zai
99
+ ```
100
+
101
+ `fleet` is the human control-plane projection; it distinguishes durable job/report state from volatile live-attempt progress and preserves the actual terminal route after completion. The live widget and footer read a bounded in-memory projection (maximum 256 durable jobs/reports and 1,000 rows) and never scan the store on their refresh timer; `fleet all` pays one bounded durable rescan, and `fleet id <id>` loads one exact durable task/report for drill-down. Whenever the view is not complete the summary ends with `· bounded`. `models` reports catalog/live broker health and credential-free provenance (`providerClass/billingPool/modelDeveloper`). Empty tier lists mean strict automatic subscription-native/current-only selection. Adding an exact model/provider entry is an explicit allowlist and is how an aggregator route is enabled. An unavailable explicit tier denies; it never falls back to the whole catalog. Delegation results show the policy fingerprint, tier, decision source, actual leased account, billing pool, freshness and failover trail. `delegate` also accepts `proposeChangesIn` plus fixed controller acceptance checks (`git-diff-check`, `npm-check`, `npm-test`, `npm-release-check`, or bounded `file-equals`) for the restricted, unapplied patch workflow; executable `argv` and caller-supplied claims are not accepted.
102
+
103
+ Read-only `delegate` and `delegate_workflow` are asynchronous by default:
104
+
105
+ ```text
106
+ delegate({ task, tier?, idempotencyKey?, deadlineMs? }) -> task id
107
+ delegate_workflow({ nodes, concurrency?, idempotencyKey?, deadlineMs? }) -> workflow id
108
+ delegate_status({ id })
109
+ delegate_list({ states? })
110
+ delegate_collect({ taskId: id })
111
+ delegate_cancel({ id, reason? })
112
+ ```
113
+
114
+ Set `wait: true` only for compatibility with a short synchronous caller. The workflow deadline remains controller safety; it does not make the parent wait. Independent workflow nodes run concurrently up to the workflow and controller limits. Each provider/account group admits four work leases plus one protected control lease by default, so fallback to one healthy subscription does not silently serialize a four-worker team; a real account-level throttle still cools that entire group. For workflows, `delegate_status` overlays volatile runner state on durable scheduler state: `running` is shown only for an executing/verifying attempt, while route selection and capacity admission remain explicit as `selecting` / `waiting_capacity` with `route:pending`.
52
115
 
53
116
  ## Minimal API shape
54
117
 
@@ -56,11 +119,17 @@ Do not install from npm until the repository release checklist is complete and t
56
119
  import {
57
120
  SingleHostBrokerSupervisor,
58
121
  BrokeredLaunchResolver,
122
+ BehavioralRunMonitor,
123
+ ControllerEvidenceStore,
124
+ ControllerAcceptanceVerifier,
125
+ validateResultEvidence,
59
126
  verifySignedRegistry,
60
127
  } from "@sars267/pi-delegation-broker";
61
128
  ```
62
129
 
63
- A supervisor requires a signed registry by default. Passing an unsigned fixture requires the explicit `allowUnsignedFixture: true` escape hatch, which exists only for deterministic tests.
130
+ `ControllerEvidenceStore` requires an absolute owner-only `root` and exactly one writer process; its startup orphan sweep is not a multi-writer protocol. Text artifacts are redacted before hashing/retention, while binary artifacts require `artifactIsRedacted: true`. `captureObservation`/`compareSemantic` support command, file-range, test-outcome and URL-body recapture, and `validateResultEvidence` applies any supplied `semanticRecaptures`. `ControllerAcceptanceVerifier` accepts no worker-provided check selection or command: its controller-owned `runCheck` closure maps a fixed 1–20 command/test plan to a read-only verifier environment; it serializes checks, captures each retained descriptor, and treats runner error, timeout, malformed observation or non-zero/failing outcome as rejected acceptance. `ControllerVerificationAuthority` retains an immutable receipt with the task/lease/fence binding; broker finalization refuses a structurally valid but unattested receipt. Volatile-field normalization is opt-in and fails with `policy_conflict` when an acceptance criterion names a stripped field. `BehavioralRunMonitor` detaches bounded lossless action/result snapshots. The broker persists the controller-selected capability under the lease, derives the state digest itself, and records behavioral events separately from provider telemetry. `extensions/pi-behavioral-enforcement.ts` retrieves that capability using only the lease-scoped IPC token, requires a one-action declaration, calls central authorization from Pi's blocking `tool_call`, and observes completed `tool_result` projections. `BrokeredLaunchResolver` requires controller-pinned extension attestation before effect-capable reservation. The implemented effect surface is intentionally only `propose_patch`: it can alter a disposable worktree, and the controller independently applies/checks the resulting patch without modifying the caller's tree. The low-level `behavioralEnforcement` constructor seam is trusted-caller-only and must never be populated from environment or configuration.
131
+
132
+ A supervisor requires signed registry schema v2 by default. Registry capacity groups must declare admission reserves, inventory confidence, default cooldown and probe interval; resources must declare their own inventory confidence. These fields are fail-closed because inferred capacity must not masquerade as measured capacity. If a durable database contains different policy values or removed/extra registry IDs, startup refuses an implicit transition and requires an audited migration/new state directory. Passing an unsigned fixture requires the explicit `allowUnsignedFixture: true` escape hatch, which exists only for deterministic tests.
64
133
 
65
134
  The controller owns all of these values:
66
135
 
@@ -71,7 +140,7 @@ The controller owns all of these values:
71
140
 
72
141
  A child receives only a short-lived lease capability. Never pass a controller token, registry signing key, OAuth token, API key or complete account inventory to child environment, prompt, result or log.
73
142
 
74
- See [`docs/INTEGRATION.md`](docs/INTEGRATION.md) for the intended boundary and [`RELEASE.md`](RELEASE.md) before publishing.
143
+ See [`docs/INTEGRATION.md`](docs/INTEGRATION.md) for the launcher boundary, [`docs/PROVIDER-PROXY-PROTOCOL.md`](docs/PROVIDER-PROXY-PROTOCOL.md) for the fail-closed provider protocol, [`docs/LIVE-VALIDATION-PLAN.md`](docs/LIVE-VALIDATION-PLAN.md) for the owner-gated live procedure, and [`RELEASE.md`](RELEASE.md) before publishing.
75
144
 
76
145
  ## Verification
77
146
 
package/SECURITY.md CHANGED
@@ -16,4 +16,4 @@ Use the repository's private GitHub Security Advisory reporting flow. If that fl
16
16
 
17
17
  ## Scope warning
18
18
 
19
- This is an alpha reference implementation. It intentionally has no real provider transport or credential storage. Do not treat its test-only fake transport, local Unix socket, lock file or signed-registry test keys as a production security boundary.
19
+ This is an alpha reference implementation. It contains a controller-only raw Anthropic transport and in-memory credential store for owner-gated experiments, but has made no live provider request and is not a production provider service. Do not treat its test-only fake transport, local Unix socket, lock file, controller proxy or signed-registry test keys as a production security boundary.
@@ -0,0 +1,40 @@
1
+ # External supervisor boundary
2
+
3
+ **Status:** required before unattended, overnight, or reboot-recovery operation. This package does not install or impersonate a host service manager.
4
+
5
+ `SingleHostBrokerSupervisor` owns the broker lock, Unix socket, TTL sweep and bounded shutdown while its controller process is alive. It is not a crash supervisor: `tmux`, `caffeinate`, a Pi session, or a shell background process cannot guarantee restart after a crash, logout, reboot, or stale process.
6
+
7
+ ## Required host-owned contract
8
+
9
+ The deployment owner must run the controller/parent process under `launchd`, `systemd`, or an equivalent reviewed service manager with:
10
+
11
+ - automatic restart after non-zero exit and reboot;
12
+ - bounded restart backoff/throttling so a broken attestation cannot hot-loop;
13
+ - an owner-only working/state directory and log destination;
14
+ - no provider credential, signing key, or approval receipt in the service unit or child environment;
15
+ - a stop path that allows the controller to execute bounded cleanup, followed by service-manager termination;
16
+ - a health signal that distinguishes `running`, `degraded` (no authorized provider), `attestation_failed`, and `stopped`;
17
+ - explicit notification/escalation when recovery leaves a task or lease ambiguous;
18
+ - a clean restart of the Pi process after source changes, because Node module reload does not re-import cached `src/*.mjs` modules.
19
+
20
+ The service manager must not be granted authority to choose a provider route, rewrite a registry, refresh a credential, or approve an effect. It only starts, stops and reports the controller process.
21
+
22
+ ## Attestation recovery
23
+
24
+ A stale launcher digest is a deliberate fail-closed condition. Recovery is:
25
+
26
+ 1. stop new delegation and let the active controller drain or reconcile;
27
+ 2. stop the parent/controller process through the service manager;
28
+ 3. review the exact extension source and update the trusted digest manifest through the approved release process;
29
+ 4. start a fresh parent/controller process so it rehashes every extension immediately before spawn;
30
+ 5. run deterministic checks and the bounded read-only canary before re-enabling work.
31
+
32
+ Never make the launcher accept the digest it just observed, disable attestation, or retry indefinitely. The failed delegation report
33
+
34
+ `Launcher attestation extension 0 digest does not match its pinned trusted digest`
35
+
36
+ must remain an operator-visible `attestation_failed` state until a reviewed restart fixes the pin.
37
+
38
+ ## Readiness boundary
39
+
40
+ A one-shot read-only canary may use the controller proxy once the live-validation plan is approved. Overnight operation additionally requires a tested service-manager configuration, restart/reconciliation evidence, and an owner who can respond to a failed or ambiguous controller state. No current repository check proves those host-level properties; they are deployment acceptance criteria, not `npm test` claims.
@@ -8,16 +8,86 @@ A compatible launcher must enforce all of the following before a brokered child
8
8
  2. Give the resolver only a prompt digest and resolved request facts, not raw prompt text.
9
9
  3. Let the resolver reserve a lease, verify the exact provider/model, create an owner-only agent directory and return a policy.
10
10
  4. Start the child with minimal inherited environment, isolated `PI_CODING_AGENT_DIR`, explicit extensions and model scope.
11
- 5. Pass only `PI_BROKER_SOCKET`, lease metadata and `PI_BROKER_CAPABILITY` to the child.
11
+ 5. Pass only `PI_BROKER_SOCKET`, lease metadata and `PI_BROKER_CAPABILITY` to the child. Do **not** serialize an authorization policy into the environment: `BrokeredLaunchResolver` must bind the exact `EffectiveChildCapability` under the lease, and the extension must retrieve it through lease-scoped IPC.
12
12
  6. Invoke parent-only hooks after child-session handoff, before-handoff failure and session-process closure.
13
+ 7. Bind the selected contract's `promptDigest` to the launch request and include its `doneWhen`; mismatched frames are denied before lease reservation.
14
+ 8. Poll/observe controller-only pending readiness and launch only the exact task/lease returned by `dispatchPending`; atomically `claimReadyTask` before spawn. A queued contract is not itself authority to spawn, and a child terminal result is never acceptance.
15
+ 9. Load `extensions/pi-behavioral-enforcement.ts` as the final explicit broker extension after every shim, with discovery disabled (`--no-extensions`) and no later extension capable of mutating tool arguments. For an effect-capable launch, pass `launcherAttestationConfig` to `BrokeredLaunchResolver`: it contains one reviewed SHA-256 for each explicit extension and the final behavioral extension path. The resolver refuses an effect before reservation if it is absent; the source-only seam rehashes every extension immediately before spawn and refuses handoff unless the child startup report includes `broker_declare_action`. It calls central `declareBehavioralAction`/`authorizeBehavioralAction` from Pi's blocking `tool_call` hook and `observeBehavioralResult` only after completed `tool_result`; the controller derives its state digest and writes behavioral events separately from provider telemetry. A parent RPC event after execution is too late to block a mismatched effect. Revalidate action snapshot, lease, fence, budget and extension/launcher attestation after any awaited hook/approval and immediately before dispatch; an extension may tighten/ask but cannot force-allow over the controller. This remains a source-pinned reference seam rather than an accepted upstream launcher API. The resolver permits only the bounded `propose_patch` surface when the final behavioral extension is attested; `apply` and `external_write` remain unavailable to children. A host must treat the local seam as owner-authorized alpha operation, not as a production sandbox. `behavioralEnforcement` is a trusted-caller-only low-level seam: do not expose it through config/env/CLI.
16
+ 10. Run the full pre-registered acceptance plan in an isolated controller verifier, then capture evidence into an owner-only retained `ControllerEvidenceStore` and validate it. `ControllerAcceptanceVerifier` is the reference for fixed command/test checks: its injected controller `runCheck` maps fixed IDs to read-only work, receives no child-selected command or expected output, runs checks serially, and rejects timeout/error/malformed/failing observations. `ControllerVerificationAuthority` then stores a receipt bound to the exact task/lease/fence; `ControllerQueuedTaskVerifier` is the controller-only close→verify→`finalizeVerifiedTask` workflow. Child-supplied `source: controller` text is not trusted without matching metadata and content. The alpha store has a single-writer precondition. Spot audit uses `captureObservation`/`compareSemantic` and passes recaptures to the validator; raw-byte recapture is not semantic comparison. Normalization is opt-in, and a criterion naming timestamp/PID/order/path conflicts with policy that strips that field.
13
17
 
14
- The [source-repository-only reference patch](https://github.com/Sarrius/pi-delegation-broker/blob/main/patches/pi-subagent-workflow-trusted-launcher-seam.patch) demonstrates such a seam against `pi-subagent-workflow` commit `0c28ce87bc45f4c3d66e0100b58ae13cf345978c`. It is an unaccepted local patch and is not part of the npm tarball. Applying it, accepting it upstream, or replacing it with an equivalent stable API remains a human release decision.
18
+ The [source-repository-only reference patch](https://github.com/Sarrius/pi-delegation-broker/blob/main/patches/pi-subagent-workflow-trusted-launcher-seam.patch) demonstrates such a seam against `pi-subagent-workflow` commit `0c28ce87bc45f4c3d66e0100b58ae13cf345978c`, including pinned extension rehash/order and startup-tool attestation. It is an unaccepted local patch and is not part of the npm tarball. Applying it, accepting it upstream, or replacing it with an equivalent stable API remains a human release decision.
15
19
 
16
- `pi-multi-account` must be absent from brokered children until it has a reviewed brokered report-only mode. A brokered child must not invoke `pi.setModel()` or auto-continue on its own.
20
+ ### Account aliases and fresh Pi processes
21
+
22
+ Parent-side account providers must remain in the controller. Extension-registered aliases are **controller inventory identities**, not necessarily standalone providers a fresh `pi --no-extensions` process can resolve. The launcher therefore has a binding canonicalization rule:
23
+
24
+ 1. The broker keeps the leased alias (for example `anthropic-account-2`) in the resource id, lease, health/cooldown accounting and audit trail.
25
+ 2. In the legacy compatibility path, `writeScopedChildAuth` reads precisely that alias credential but writes it as its base id (`anthropic`) into the child-only `auth.json`, catalog and endpoint config. This remains model-readable and is not permitted for live validation.
26
+ 3. In the approved proxy path, no child credential file is written: the controller route resolver binds the alias to its in-memory credential reference and the child launches `--provider broker-proxy` with only a non-secret model id.
27
+ 4. `resolveChildLaunchModel` in the legacy path launches `--provider anthropic --model claude-opus-5`. Since that agent directory contains exactly one credential, the canonical id cannot accidentally select the parent's base account or any other alias.
28
+
29
+ Do not pass `*-account-N/model` to a fresh isolated Pi child and do not solve this by loading the parent's provider-management extensions: either choice breaks the scoped-credential boundary. Regression coverage lives in `test/scoped-child-auth.test.mjs` and `test/child-launcher.test.mjs`.
30
+
31
+ ### Proxy-backed OAuth (Cursor)
32
+
33
+ A controller may have a Cursor provider registered at `http://127.0.0.1:<port>/v1`. A bare `--no-extensions` child cannot run Cursor's OAuth handler. The approved native controller path resolves the exact Cursor model and OAuth credential in the parent, invokes Pi's native provider with retries disabled and forces SSE through the owner-run local Cursor bridge. Approved tool schemas and tool-call/result replay remain inside this credentialless boundary. The older `createApprovedOpenAIProviderRoute` path is retained for explicit text-only compatibility tests. The child receives only `broker-proxy` and never gets `auth.json`. The loopback endpoint is accepted only for this explicitly configured adapter; arbitrary HTTP remains rejected. The legacy `writeScopedChildAuth` path still materializes Cursor as `type: api_key` for compatibility and is not live-validation-ready. Regression coverage: `test/scoped-child-auth.test.mjs`, `test/child-rpc.test.mjs`, `test/openai-chat-completions-transport.test.mjs` and `test/live-cursor-controller-proxy-child.test.mjs`.
34
+
35
+ Other review tools that launch their own Pi processes must configure their own provider access. The broker does not configure those tools or load account-management extensions into children. A brokered child cannot switch the controller model or independently initiate failover.
36
+
37
+ ### Reloading during development
38
+
39
+ `/reload` re-runs extension activation, but it does **not** re-import `src/*.mjs`: Node caches ES modules by URL for the lifetime of the process, so a reloaded extension keeps calling the already-loaded broker code. Editing anything under `src/` therefore requires a full Pi restart, not a reload.
40
+
41
+ This matters because the failure is silent — the extension activates, the broker starts, and stale routing or health logic keeps running as if the fix were live. Do not conclude from a reloaded session that a `src/` change had no effect. Confirm which code is actually running by observing a field only the new code can emit; for example `RegistryReloaded` carries `healthCarried` only after the health-preserving reload landed. An audit event is evidence, an assumption is not.
42
+
43
+ ### Credentialless controller-provider proxy
44
+
45
+ The extension builds an approved `providerTransport + routePreflight + routeResolver` pair from public Pi APIs. With that pair, `BrokeredLaunchResolver` can be constructed with `controllerProxy: { providerId: "broker-proxy" }` and **without** `provisionChildAuth`. Before reserving a lease, the resolver refreshes the owner-side provider snapshot and asks `routePreflight` to validate each exact candidate's current model registration, native runtime, credential availability and API dialect. Unready candidates are quarantined and excluded without starting a child or spending a physical attempt. It then launches the leased resource's model through `extensions/controller-provider-proxy.ts`; the child gets only `PI_BROKER_SOCKET`, its lease-scoped capability and a non-secret model id. There is no child `auth.json`, upstream endpoint credential, OAuth token or parent agent directory.
46
+
47
+ The proxy calls child-authorized `providerStream` over the owner-only Unix socket. The native boundary accepts bounded text, canonical inline base64 image blocks (`png`, `jpeg`, `gif`, `webp`), approved JSON-schema tools, assistant tool calls and matching tool results. A child advertises image input only when its contract requires `vision_input`, and the parent-native adapter independently verifies that the exact selected model supports images. Remote image URLs, unknown media, provider options, reasoning replay, unknown fields and unapproved tool names are rejected before dispatch; executable functions and provider credentials never cross the boundary. The older hand-built OpenAI/Anthropic adapters remain text-only and reject image replay before credential resolution. The controller remains responsible for immutable route snapshots, one physical dispatch, provider health classification and all retry/failover decisions. The extension constructs its own native adapter from public Pi registry/auth/runtime methods. An explicitly injected owner transport remains a library/test seam, not a sibling-extension integration. Hosts without the required Pi APIs retain only the documented compatibility path; a missing live transport never becomes fake success.
48
+
49
+ `test/tool-capable-proxy-child.test.mjs` is a deterministic E2E: a real Pi child runs the native `broker-proxy` provider against `ScriptedFakeProvider`; it asserts a local tool call/result round-trip and no child `auth.json`. The owner-authorized fresh-process Cursor canary additionally exercises a real native child read and isolated patch/test flow. A real provider route still requires the owner-gated process in [the provider route protocol](PROVIDER-PROXY-PROTOCOL.md), never ambient authentication.
50
+
51
+ ## Controller provider route gate
52
+
53
+ `ControllerCredentialStore` keeps one exact API key or short-lived OAuth access token only in controller memory. `ControllerRouteTable` is an owner-only, credential-free JSON configuration that maps one signed resource to exactly one account/model/endpoint/opaque credential reference. `createApprovedAnthropicProviderRoute` and `createApprovedOpenAIProviderRoute` require a matching `ControllerLiveProviderApproval` with a bounded expiry/request count before they can dispatch. They expose no account rotation, default route, ambient auth, SDK retry or failover. Anthropic subscription OAuth is sent as Bearer auth with the Claude Code identity headers; the Cursor path sends Bearer auth to an owner-run loopback OpenAI-compatible bridge; refresh tokens are never forwarded. A real call still requires a separately authorized/pre-registered live experiment; mocked adversarial tests do not establish a live capability.
54
+
55
+ `ControllerVerifiedRoutingBoard` may reorder only the broker's current signed candidate IDs. It updates from receipt-bound controller acceptance/rejection after terminal finalization; it cannot add a resource, bypass capability/budget/breaker checks, or treat child/provider self-report as routing evidence.
17
56
 
18
57
  ## Failure semantics
19
58
 
20
59
  - Before handoff: controller release cleans up the lease/agent directory. If controller IPC fails, the resolver retains an explicitly pending record for later reconciliation.
21
- - After handoff: child provider shutdown releases its lease; parent completion cleanup is idempotent.
60
+ - After handoff: child provider shutdown/session close releases its lease and moves a claimed queued task to `awaiting_result`; parent cleanup cannot complete it. Only controller `finalizeVerifiedTask` with an independent verifier receipt can make it `completed` or `failed`; absent reconciliation escalates at the registered deadline.
61
+ - Capacity unavailable: `submit` queues only when the contract names a recovery owner and future deadline. Release/expiry/cooldown transitions make timed work eligible; the supervisor scheduler moves admitted entries to `ready`, while the controller remains responsible for child launch.
62
+ - Wait deadline reached: the task becomes `escalated`; it never remains an ownerless, unbounded `paused_capacity` row.
22
63
  - Provider transport missing: `BrokerIpcServer` returns `provider_transport_unavailable`; it never falls back to fake success.
23
64
  - A real provider proxy must classify and reconcile any ambiguous external effect. The fake transport only proves pre-effect cancellation.
65
+
66
+ ## Delegation product contract (2026-09-05)
67
+
68
+ The controller optimizes accepted useful work. A self-contained subtask alone is not a reason to delegate. The parent weighs briefing, child latency, independent parallel work and verification/integration effort; tiny or tightly coupled tasks stay local.
69
+
70
+ New model-facing `delegate` and workflow node schemas require `work`: `taskClass` (`lookup`, `summary`, `research`, `diagnosis`, `implementation`, `review`, `general`), `deliverable`, `benefit`, `parentWork`, optional `purpose` (`produce` or `review`), `ownedPaths` (absolute paths), and `maxAttempts` (1..8, default 3). Old persisted jobs and direct programmatic callers without this field remain readable/compatible. A child still needs its complete self-contained task brief; the work plan does not grant tools or change authorization.
71
+
72
+ The declared result belongs to the child while active. The parent receives bounded ownership reminders before each turn and continues only independent work. Parent `write` and `edit` calls to declared production paths are blocked until terminal state. This is a typed-tool guard plus a behavioral contract: arbitrary shell operations and semantic duplication without declared paths are not universally detectable. Independent review is explicit. Parallel workflow producers cannot own overlapping paths; an explicit dependency permits sequential ownership. Workflow default concurrency is two.
73
+
74
+ Cancellation is a request until terminal state. A parent taking over must first cancel, then inspect `delegate_status` until terminal. Collect a completed result before integration. Submission and job updates use a cross-process SQLite lock; identical active task fingerprints within an owner session reuse the existing job. A later new request after terminal completion can run again. Replaying an existing job does not launch another local controller's execution. Simultaneous processes deliberately resuming the identical session still require the existing recovery caveat; submission idempotency does not prove exactly-once execution across all recovery paths.
75
+
76
+ `delegate_inventory({provider?, history?})` exposes credential-free model identities, quality tiers, policy exclusions, observed health, group cooldown, free child slots and confidence. A bounded summary enters parent context automatically once the runtime is available. The snapshot is advisory; the controller rechecks route readiness and atomically leases at launch. Unknown, probe-eligible, busy, cooling and excluded are distinct. The selector's ordered allowed-resource list now reaches lease admission; a busy preferred resource can only fall back within that vetted list. Reserved controller concurrency is reported. Subscription quota headroom for the ordinary parent is not measurable and is never advertised as guaranteed protection from provider limits.
77
+
78
+ `routing-history.sqlite` is a shared controller-only WAL ledger. It survives process replacement, serializes concurrent writes and rejects conflicting event-id reuse. Events contain route identifiers, outcome labels and consumption, not prompts, credentials or raw provider errors. Old per-controller affinity snapshots are imported idempotently as non-comparable legacy aggregates; they cannot train new cohorts because they lack task/effort provenance. Old snapshots remain on disk.
79
+
80
+ Verified learning cohorts use canonical provider/model identity (account aliases pool model evidence), required capabilities, explicit task class, requested effort and routing contract version `routing-v3`. Route history also retains effective effort; requested and effective effort are not equated. Update the cohort version when changing the interpretation of acceptance/effort, rather than silently combining incompatible evidence. Ranking uses recent observations (30 days, bounded recent-event query), a Beta(1,1) accepted-rate estimate and neutral 0.5 for fewer than three observations. Quality is primary in 0.05 bands; consumption breaks ties. Sample count no longer multiplies quality. Missing token observations remain unknown. The performance view includes sample counts and Wilson 95% intervals; neither small samples nor different task classes establish a universal model leaderboard.
81
+
82
+ Only authenticated controller task checks and controller scratch-tree patch checks feed quality. Transport/quota errors go to route history, not model-quality rejection counts. Independent acceptance checks are required before low-risk `lookup`/`summary` work can explore an eligible under-observed model: at most one in ten qualifying selection decisions, serialized across controllers. Repeated selection for the same task and inventory previews do not buy extra exploration. Ineligible/cooling models are never restored by learning. Other task classes do not autonomously explore merely to balance traffic; absence of evidence remains unknown.
83
+
84
+ `delegate_feedback({taskId, utility, parentIntegrationMs?})` records one final parent-attributed integration outcome: `used`, `reworked`, `redundant`, `unused`. It cannot impersonate an independent quality verdict or train verified scores. `delegate_inventory({history:true})` shows current-session route/usefulness history and shared verified cohort statistics. `/delegation-broker history` gives the human the shared performance and recent route view, also when the runtime is stopped.
85
+
86
+ Money is explicitly unavailable unless a future authoritative cost adapter supplies it. Tokens, retries and observed time are separate measurements, not fabricated subscription prices. A completed child execution is not automatically verified, and verified work is not automatically useful. Long-term savings require repeated field observations of accepted owner results, including parent integration effort; deterministic lifecycle tests do not establish that economic claim.
87
+
88
+
89
+ ### Independent extensions
90
+
91
+ Agent Pi is the common host contract. Broker source imports no sibling extension and reads no sibling's private state. It discovers providers from Pi's registry/auth configuration and manages its own observed health and leases. There is no private catalog or account-health event channel between extensions. Consequently a provider's quota may remain unknown until the broker observes a request/probe; unknown capacity must remain visible as unknown.
92
+
93
+ The standalone fresh-Pi regression loads only a scripted Pi provider and this broker, injects no controller pair, and completes a real isolated child through the broker-owned native adapter. The separate combined-package smoke checks coexistence only. Multi-account's legacy only-active preference no longer removes registered models from the shared Pi registry, so all independent Pi clients see the same catalog.
@@ -0,0 +1,44 @@
1
+ # Owner-gated live validation plan
2
+
3
+ **Status:** pre-registered procedure with owner-authorized canary evidence. The Anthropic subscription arm was rejected by Anthropic's extra-usage policy; the Cursor subscription arm completed successfully through the controller proxy. A separate fresh-process native Cursor tool canary completed a credentialless read and isolated patch/test flow. This document is not standing approval for future requests.
4
+
5
+ ## Preconditions
6
+
7
+ All conditions must be true before the controller constructs any approved provider route with a non-test credential:
8
+
9
+ 1. A reviewed first-party trusted launcher API (or accepted upstream replacement for the local patch) proves final behavioral-hook attestation. Effect-capable contracts remain denied until then.
10
+ 2. A signed registry identifies the one approved resource/profile/capacity group. The route table maps that same resource to one account/model/endpoint and opaque credential reference; its route-table fingerprint is recorded.
11
+ 3. The exact provider credential is injected into `ControllerCredentialStore` by the owner process only: either an API key or the current OAuth access token. It must not appear in config, environment, Pi child config, prompt, logs, evidence, registry or CLI arguments; OAuth refresh tokens are never forwarded.
12
+ 4. The owner supplies a one-request `ControllerLiveProviderApproval` expiring within the pre-registered window. The controller records approval ID, route-table fingerprint and wall-clock bounds without recording a secret.
13
+ 5. The standing live task is non-effectful/read-only, bounded by a lease, controller budget, known prompt digest, fixed verifier plan and a named recovery owner/deadline. Effect-capable canaries are a separate owner-authorized experiment and require the source-pinned behavioral launch gate plus isolated worktree verification.
14
+
15
+ The credentialless Pi-child canary harnesses are `test/live-controller-proxy-child.test.mjs` (Anthropic Messages) and `test/live-cursor-controller-proxy-child.test.mjs` (Cursor's OpenAI-compatible loopback bridge). The tool-capable native experiment must load `pi-multi-account` in the fresh parent and inject its `controllerProvider` pair; the older hand-built OpenAI/Anthropic adapters remain text-only. Both remain skipped unless the owner explicitly sets `LIVE_PROVIDER_TEST=1`, `LIVE_PROXY_CANARY_APPROVED=1`, and `LIVE_CONTROLLER_CREDENTIAL_FILE` to an existing owner-only file. The file may contain a raw API key, a single credential JSON object (`{"type":"oauth","access":"..."}` or `{"type":"api_key","key":"..."}`), or the owner-only Pi `auth.json`; for the latter, `LIVE_CONTROLLER_CREDENTIAL_PROVIDER` selects the exact provider. Only the selected access/key value is injected into the in-memory `ControllerCredentialStore`; it is never passed to the child, route config, CLI arguments, or evidence. Anthropic OAuth requests use Bearer auth plus Claude Code identity headers, including `oauth-2025-04-20`; Cursor OAuth is sent as Bearer auth to the owner-run loopback bridge; refresh tokens are ignored. `LIVE_ANTHROPIC_MODEL`/`LIVE_CURSOR_MODEL` select the model, while `LIVE_ANTHROPIC_ENDPOINT`/`LIVE_CURSOR_ENDPOINT` select the exact endpoint. The Cursor endpoint must be an owner-run loopback adapter. These harnesses remain prohibited until preconditions 1–5 are independently approved for each new run.
16
+
17
+ ## Completed native tool-capable evidence
18
+
19
+ Owner-authorized fresh parent processes loaded `pi-multi-account`, injected its native `controllerProvider` pair, and ran the broker against temporary Git projects on the exact registered Cursor route `cursor/claude-4-sonnet`. A signed-registry credentialless child completed a local `read` turn with zero child auth files and zero leases. A separate signed-registry effect-capable child used only the attested `propose_patch` tool in a disposable worktree; the controller independently applied the returned patch and its fixed `npm-test` check passed, with zero leases. The native child tool sets included only requested/local tools plus controller gate tools. This is bounded path evidence, not standing approval: the parent-run Cursor bridge must remain available, legacy direct-auth paths remain disallowed for live coding, and unattended supervision is outside this plan.
20
+
21
+ ## Adversarial gate sequence
22
+
23
+ Run one physical attempt per case; never re-run a case through another account/model automatically.
24
+
25
+ | Case | Required result | Automatic follow-up |
26
+ |---|---|---|
27
+ | credential ref miss while unrelated ambient secret exists | no dispatch; `credential_unavailable` | deny/escalate |
28
+ | aborted before dispatch | `cancelled_before_send`; no dispatch | controller may decide new attempt |
29
+ | transport loss after send | `cancelled_after_send` or ambiguous provider terminal | no retry; reconcile/escalate |
30
+ | provider 429 with Retry-After | one dispatch; `rate_limited`; exact capacity-group cooldown | no automatic failover |
31
+ | quota/auth terminal | one dispatch; exact resource becomes unknown | deny/pause |
32
+ | malformed/truncated SSE | no success terminal; controller evidence retains failure | no retry after send |
33
+ | valid simple read-only completion | one dispatch; independent fixed verifier accepts; task becomes completed | record verifier-bound routing observation |
34
+
35
+ For every case retain redacted controller evidence of route snapshot fingerprints, dispatch count, normalized terminal, broker ledger transition, and verifier result. Do not use child status or provider text as acceptance evidence.
36
+
37
+ ## Paired Pi utility experiment
38
+
39
+ Only after the valid controller-owned arm above completes, pre-register a paired Pi experiment. Each pair uses the same read-only task, model/reasoning setting, fixed acceptance verifier, input/output-token and latency budget, and one physical attempt per arm:
40
+
41
+ - **A:** isolated Pi child through the approved controller route.
42
+ - **B:** baseline Pi configuration, with the same model/task and independently retained acceptance evidence.
43
+
44
+ Report accepted outcomes, dispatch count, wall time, token usage, terminal taxonomy and confidence intervals; do not claim a rate from one pair. A Claude Code arm is outside this plan and remains deferred until A is a valid executable live arm.