Skip to content

Commit 689a10a

Browse files
committed
docs(egress-gate): add Pi admission example
1 parent c841c70 commit 689a10a

5 files changed

Lines changed: 413 additions & 4 deletions

File tree

projects/egress-gate/README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ commands work from any directory and do not depend on repository-only files:
2424
egress-gate gates list
2525
egress-gate gates schema
2626
egress-gate validate --policy /absolute/path/to/your-policy.yaml
27-
egress-gate serve --listen 127.0.0.1:50051
27+
egress-gate serve --listen 127.0.0.1:50051 --no-require-pi-receipt
2828
```
2929

3030
## Source-checkout quickstart
@@ -39,7 +39,7 @@ uv run egress-gate gates list
3939
uv run egress-gate gates schema
4040
uv run egress-gate validate \
4141
--policy examples/regex-redaction/egress-gate-config.yaml
42-
uv run egress-gate serve --listen 127.0.0.1:50051
42+
uv run egress-gate serve --listen 127.0.0.1:50051 --no-require-pi-receipt
4343
uv run egress-gate evaluate \
4444
--policy examples/regex-redaction/egress-gate-config.yaml \
4545
--cases examples/regex-redaction/cases.yaml
@@ -49,6 +49,13 @@ Use `0.0.0.0` only when the OpenShell supervisor must reach the service across
4949
network namespaces. The development server uses plaintext gRPC. Restrict its
5050
listen port to trusted networks.
5151

52+
The CLI requires managed Pi admission receipts by default, coupling receipt
53+
issuance to provider egress verification. The general Gate quickstarts opt out
54+
explicitly. Keep the default, or pass `--require-pi-receipt`, for managed Pi;
55+
use `--no-require-pi-receipt` only for an intentionally unmanaged deployment.
56+
See the [managed Pi example](examples/pi-attested-admission/README.md) for the
57+
matching Pi and OpenShell fork branches, startup contract, and current limits.
58+
5259
## Policy shape
5360

5461
The registry builds an exact strict schema from installed gate types:
@@ -87,7 +94,7 @@ need initialization, helper bases, or typed resources use the full class-based
8794

8895
```bash
8996
uv run egress-gate --registry my_gates:registry gates list
90-
uv run egress-gate --registry my_gates:registry serve
97+
uv run egress-gate --registry my_gates:registry serve --no-require-pi-receipt
9198
```
9299

93100
OpenShell owns interception, routing, and credential attachment. Egress Gate
@@ -103,11 +110,14 @@ from egress_gate.service import EgressGateServer
103110
server = EgressGateServer(
104111
create_builtin_registry(),
105112
timeout_middleware_processing=10,
113+
require_pi_receipt=False,
106114
)
107115
server.serve_sync("127.0.0.1:50051")
108116
```
109117

110-
In this example, `timeout_middleware_processing` gives each evaluation 10
118+
Make the `require_pi_receipt` choice explicit in programmatic deployments; set
119+
it to `True` for managed Pi. In this unmanaged example,
120+
`timeout_middleware_processing` gives each evaluation 10
111121
seconds. Omitting it uses the one-second service default. The value is expressed
112122
in seconds, must be at least 10 milliseconds, and must resolve to whole
113123
milliseconds. The service passes one resulting `Timeout` through slot
@@ -136,6 +146,7 @@ timeout failures must deny.
136146
- [Architecture](https://github.com/NVIDIA/OpenShell-Research/blob/main/projects/egress-gate/docs/architecture/index.md)
137147
- [Limits and failures](https://github.com/NVIDIA/OpenShell-Research/blob/main/projects/egress-gate/docs/reference/limits-and-failures.md)
138148
- [Regex redaction composition](https://github.com/NVIDIA/OpenShell-Research/tree/main/projects/egress-gate/examples/regex-redaction)
149+
- [Pi attested-admission example](examples/pi-attested-admission/README.md)
139150
- [Function-based custom gate](https://github.com/NVIDIA/OpenShell-Research/tree/main/projects/egress-gate/examples/custom-gate)
140151
- [Class-based custom gate](https://github.com/NVIDIA/OpenShell-Research/tree/main/projects/egress-gate/examples/class-based-gate)
141152

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
gates:
2+
- name: deny-marker
3+
kind: regex
4+
scan:
5+
kind: body
6+
action:
7+
kind: deny
8+
pattern_catalog:
9+
entities:
10+
- name: unsafe-marker
11+
rules:
12+
- name: exact-deny-marker
13+
pattern: OPEN_SHELL_ADMISSION_DENY_TEST
14+
confidence: high
15+
- name: replace-marker
16+
kind: regex
17+
scan:
18+
kind: body
19+
action:
20+
kind: replace
21+
template: "[REDACTED]"
22+
pattern_catalog:
23+
entities:
24+
- name: replacement-marker
25+
rules:
26+
- name: exact-replacement-marker
27+
pattern: OPEN_SHELL_ADMISSION_REPLACE_TEST
28+
confidence: high
29+
default_decision: allow

0 commit comments

Comments
 (0)