# Pi-local AHP bridge

Paratenic's Pi extension installs a package-neutral bridge at:

```js
const bridge = globalThis[Symbol.for("paratenic.pi.ahp-bridge.v1")];
```

The bridge is optional. `PI_DISABLE_AHP=1` prevents Paratenic registration and bridge installation. `PI_DISABLE_ACP=1` is accepted as a migration alias. Consumer extensions must remain fully functional when the bridge is absent.

The bridge knows only Pi-local runtime ownership and AHP-shaped input state. It contains no consumer package names, tool names, or package-specific result interpretation.

## Discovery

The frozen bridge object has `version: 1`, `enabled: true`, a current optional `registration` getter, `capabilities()`, `onRegistrationChanged(listener)`, and `registerInputProvider(provider)`.

Consumers loaded after Paratenic read the global symbol. Consumers loaded first may listen for `paratenic:ahp-bridge-available`. They may emit `paratenic:ahp-bridge-discovery` after reload; Paratenic responds with the same bridge object.

Registration contains `hostId`, `session`, `defaultChat`, and `backendInstanceId`. Listeners receive the complete current value or `undefined`; they do not infer state from socket events.

## Managed runtime binding

When a typed managed Pi launch supplies `CACO_MANAGED_PI_AGENT=1`, `CACO_AGENT_ID`, and `CACO_RUNTIME_GENERATION`, the owner-private `PI_AHP_BINDING_FILE` uses schema version 2 and includes the exact values as `managedAgentId` and `runtimeGeneration`. If the compatibility alias `CACOPHONY_AGENT` is present, it must exactly match `CACO_AGENT_ID`. The opaque runtime generation is nonempty and at most 128 bytes; it has no ordering semantics. The extension captures this evidence when its runtime loads and publishes it atomically alongside `backendConnectionId`, `backendInstanceId`, and `registeredAt`.

The two managed fields are all-or-nothing. Unmanaged launches, legacy launches, launches missing either value, and launches with conflicting agent aliases continue to publish a readable schema-version-1 binding without managed owner evidence. Consumers requiring generation-fenced delivery must reject those legacy bindings; display names, session names, paths, and unrelated launcher fingerprints are not owner evidence. A Pi runtime restart receives a new runtime generation and therefore cannot reuse its predecessor's binding as current evidence.

## Input providers

A provider registration has this structural shape:

```js
const handle = bridge.registerInputProvider({
  version: 1,
  providerId: "opaque-process-local-id",
  questionKinds: [
    "text",
    "number",
    "integer",
    "boolean",
    "single_select",
    "multi_select",
  ],
  freeform: true,
  drafts: false,
  maxQuestions: 16,
  maxOptions: 64,
  snapshot: () => completePendingRequests,
  complete: async command => ({ accepted: true }),
});
```

`providerId`, request IDs, question IDs, option IDs, command IDs, and operation IDs are opaque identities. Labels are never identities. Strings, provider counts, pending requests, question counts, options, and backend frames are bounded.

The handle publishes runtime-authoritative state:

```js
handle.requested(request);
handle.updated(request);
handle.resolved({
  requestId,
  resolution: "accept", // decline, cancel, timeout, superseded
  answers,
  commandId,
  source,
});
handle.dispose();
```

Paratenic validates the neutral envelope and routes incoming `complete_input` to the exact provider that owns the request. It passes `operationId`, `commandId`, `requestId`, `response`, and `answers` without interpreting their domain meaning. Provider admission and final `resolved` settlement are distinct. Backend command replay is idempotent, and a request ID cannot be owned by two providers.

Provider snapshots are authoritative after reconnect. Missing snapshot entries remove stale queued requests at the snapshot cut. Disposing a provider cancels its remaining bridge projections without invoking another package's local behavior.

Provider requests are runtime-owned: bridge v1 does not assign a transcript `turnId`.
They can coexist, be restored while idle and remain pending while later Pi turns run.
The host projects them through the standard session input-needed queue; native transcript replacement does not own their lifecycle.
Only provider resolution, an available authoritative snapshot omitting the ID, or provider disposal retires them.
The host's fresh session catalogue must continue to report `InputNeeded` whenever that independent queue contains user requests, otherwise Culture correctly refuses answers with `session is absent from the fresh input-needed catalogue`.

Older hosts assumed every input belonged to the active turn and rebuilt their status from transcript activity alone.
The JavaScript registration bridge could accept concurrent requests, but that did not prove host lifecycle compatibility: an idle replay could be rejected or a pending form could become non-actionable after a source snapshot.
`tests/pi_control_boundary.rs` now exercises the real JS provider/transport against the host and Culture's shared input core, including idle posting, an intervening native turn, exact answer delivery and withdrawal.

Draft capability remains disabled in version 1 until the backend protocol has a correlated runtime draft command. Consumers may expose local drafts independently.

## Sessions hosted by another AHP host

Sessions owned by another AHP host (for example pi-daemon 1.x) do not use this
bridge and are not hosted by Paratenic. Clients connect to that host directly,
receive its `chat/inputRequested` actions and answer there with
`chat/inputCompleted`.

## Future providers

Attachment/resource and external-terminal registration are not implemented by bridge v1. They will extend this same bridge after their bounded binary transport and sequence/resynchronization contracts land. They must not introduce consumer-specific event names or a second durable state owner.

The [portable-image audit and proposed AHP contract](portable-images.md) specifies the existing AHP 0.8 carriers, durable host-owned resource import, separate direct-Pi and Pi-Daemon history paths, and shared Culture loading/rendering requirements. A resource-import receipt and a durably shared transcript reference are separate milestones. Local kitty preview state remains local unless explicitly shared; actual Pi conversation image blocks should be forwarded generically, without tool-name-specific integration.
