|
| 1 | +# Pi attested-admission example |
| 2 | + |
| 3 | +This credential-free example exercises Egress Gate's public harness-admission |
| 4 | +and attested-egress APIs across the state boundaries a managed Pi runtime must |
| 5 | +enforce. It uses the real configured regex Gates, admission processor, Pi shape |
| 6 | +adapter, Ed25519 receipt issuer, provider adapter, network Gate pass, and |
| 7 | +receipt-header stripping. The deterministic provider recorder is local; no API |
| 8 | +key or external service is needed. |
| 9 | + |
| 10 | +From `projects/egress-gate/`, run: |
| 11 | + |
| 12 | +```bash |
| 13 | +uv run python examples/pi-attested-admission/run_example.py \ |
| 14 | + --session-file /tmp/pi-egress-example/session.jsonl |
| 15 | +``` |
| 16 | + |
| 17 | +The command prints JSON evidence for the intentionally small MVP: |
| 18 | + |
| 19 | +- a safe idle, text-only rendered prompt and its first provider request; |
| 20 | +- denial before the candidate changes the session or reaches the provider; |
| 21 | +- candidate replacement before persistence and provider serialization; |
| 22 | +- fail-closed denial of an unattested continuation; and |
| 23 | +- removal of the internal receipt header before the provider recorder. |
| 24 | + |
| 25 | +Inspect the resulting accepted history with: |
| 26 | + |
| 27 | +```bash |
| 28 | +python3 -m json.tool --json-lines /tmp/pi-egress-example/session.jsonl |
| 29 | +``` |
| 30 | + |
| 31 | +The output reports receipt, canonicalization, provider-adapter, active key ID, |
| 32 | +and policy versions, but never prints receipt bytes or denied content. |
| 33 | + |
| 34 | +This hermetic executable is the Egress Gate component layer of the broader Pi |
| 35 | +integration. `ManagedPiSession` deliberately models the required ordering: |
| 36 | +rendered-prompt admission, optional candidate replacement, candidate commit, then |
| 37 | +attested network egress. It is not presented as the pinned downstream Pi fork |
| 38 | +or the full OpenShell sandbox layer; those runtime artifacts must use the same |
| 39 | +public API and preserve this ordering. |
| 40 | + |
| 41 | +## Run the managed forks |
| 42 | + |
| 43 | +Use the matching integration branches: |
| 44 | + |
| 45 | +- [Pi `openshell/pi-egress-admission`](https://github.com/johnnygreco/pi/tree/openshell/pi-egress-admission) |
| 46 | +- [OpenShell `openshell/pi-egress-admission`](https://github.com/johnnygreco/OpenShell/tree/openshell/pi-egress-admission) |
| 47 | + |
| 48 | +Register this service as an OpenShell supervisor middleware and start it without |
| 49 | +`--no-require-pi-receipt`. Configure exactly one network middleware entry for |
| 50 | +the OpenAI provider host. When OpenShell sees that the service advertises the |
| 51 | +Pi admission binding, it exposes the loopback bridge and sets |
| 52 | +`OPENSHELL_PI_CONVERSATION_URL` in the sandbox. The pinned Pi fork detects that |
| 53 | +variable and loads its bundled `openshell-input-admission.ts` extension. A |
| 54 | +normal Egress Gate deployment that does not use managed Pi must start with |
| 55 | +`--no-require-pi-receipt`; it advertises and evaluates only HTTP middleware. |
| 56 | + |
| 57 | +The managed path currently supports direct OpenAI Chat Completions requests |
| 58 | +from the pinned Pi serializer. It does not support images, steering or queued |
| 59 | +follow-ups while streaming, compaction requests, provider retries, or automatic |
| 60 | +continuations after tool calls. Those paths fail closed. The next increment is |
| 61 | +a separate pre-provider-request admission boundary that issues one receipt for |
| 62 | +each automatic call; it does not change the rendered-prompt hook or its |
| 63 | +pre-persistence denial guarantee. |
| 64 | + |
| 65 | +Version 1 supports the direct OpenAI Chat Completions subset emitted by the |
| 66 | +pinned Pi serializer: text messages, function tools and calls/results, |
| 67 | +`max_completion_tokens`, optional `temperature` and `reasoning_effort`, tool |
| 68 | +choice, `stream: true`, `stream_options.include_usage: true`, `store: false`, |
| 69 | +and optional `prompt_cache_key` and `prompt_cache_retention: "24h"` cache |
| 70 | +fields. Compatibility-provider fields, custom sampling parameters, unknown |
| 71 | +fields, unsupported content variants, and lossy multipart forms fail closed. |
| 72 | +The provider adapter accepts either a string or one OpenAI text |
| 73 | +block for message content because the pinned fixture treats those as the same |
| 74 | +single text value. It otherwise requires one representation: `content` is |
| 75 | +present, optional message metadata is omitted instead of `null`, and empty tool |
| 76 | +call arrays are omitted. Integer, floating-point, and negative-zero spellings of |
| 77 | +the same temperature are normalized because the pinned fixture treats them as |
| 78 | +one numeric value. Provider requests require exactly one parameter-free |
| 79 | +`Content-Type: application/json` header and no `Content-Encoding`. |
| 80 | + |
| 81 | +Each receipt is short-lived and consumed by the first matching provider |
| 82 | +request. It binds the admitted rendered prompt, sandbox, middleware policy, and |
| 83 | +provider target. It does not prove which JavaScript extension called the |
| 84 | +supervisor bridge, and it does not attest the complete conversation or provider |
| 85 | +payload. OpenShell reruns the configured Gates on the actual HTTP request before |
| 86 | +forwarding it and strips the internal receipt header. |
0 commit comments