Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
faf3093
feat(egress-gate): add attested Pi admission
johnnygreco Aug 12, 2026
021281f
docs(egress-gate): add Pi admission example
johnnygreco Aug 12, 2026
c5601c0
fix(egress-gate): own Pi integration extension
johnnygreco Aug 12, 2026
355db91
refactor(egress-gate): use user message append hook
johnnygreco Aug 12, 2026
9d53095
refactor(egress-gate): focus Pi example on deny and redact
johnnygreco Aug 12, 2026
22c93e6
docs(egress-gate): replace simulated Pi example
johnnygreco Aug 17, 2026
0540f54
fix(egress-gate): clean up Pi admission integration
johnnygreco Aug 17, 2026
35ec55c
docs(egress-gate): simplify Pi admission demo
johnnygreco Aug 20, 2026
de716d7
chore: add example license headers
johnnygreco Aug 20, 2026
c8be825
chore: ignore local planning files
johnnygreco Aug 24, 2026
80b268d
chore(egress-gate): merge upstream main
johnnygreco Aug 26, 2026
acebc5d
docs(egress-gate): sync Pi example forks
johnnygreco Aug 26, 2026
7f71b16
docs(egress-gate): streamline Pi example setup
johnnygreco Aug 26, 2026
324d744
fix(egress-gate): isolate nested OpenShell checkout
johnnygreco Aug 27, 2026
f512f94
feat(egress-gate): complete Pi attested admission example
johnnygreco Aug 28, 2026
252a977
fix(egress-gate): recreate example provider profile on launch
johnnygreco Aug 28, 2026
18158b9
fix(egress-gate): redact model credentials from Pi tool output
johnnygreco Aug 28, 2026
fb51e76
fix(egress-gate): distinguish credential placeholders from secrets
johnnygreco Aug 28, 2026
8d1b739
fix(egress-gate): allow maximum Pi request payloads
johnnygreco Aug 28, 2026
8d9f96b
feat(egress-gate): isolate managed Pi admission
johnnygreco Aug 28, 2026
d2930e9
refactor(egress-gate): run admitted Pi through standard CLI
johnnygreco Sep 1, 2026
6caa8de
Raise Pi example model token limits
johnnygreco Sep 1, 2026
eb76919
Configure three Pi example models
johnnygreco Sep 1, 2026
67b4642
Use standard Pi models configuration
johnnygreco Sep 1, 2026
5638f7c
refactor(egress-gate): run configured Pi entrypoint
johnnygreco Sep 1, 2026
73b6114
fix(egress-gate): preserve standard Pi extensions
johnnygreco Sep 1, 2026
693ccfe
feat(egress-gate): attest OpenAI Responses context
johnnygreco Sep 1, 2026
9cf1560
refactor(egress-gate): run standard persistent Pi demo
johnnygreco Sep 1, 2026
cd271bb
fix(egress-gate): default Pi demo to empty workspace
johnnygreco Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ temp/
*.temp
*.bak
.scratch/
plans/

# Python
__pycache__/
Expand Down
1 change: 1 addition & 0 deletions projects/egress-gate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.workspaces/
19 changes: 15 additions & 4 deletions projects/egress-gate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ commands work from any directory and do not depend on repository-only files:
egress-gate gates list
egress-gate gates schema
egress-gate validate --policy /absolute/path/to/your-policy.yaml
egress-gate serve --listen 127.0.0.1:50051
egress-gate serve --listen 127.0.0.1:50051 --no-require-pi-attestation
```

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

The CLI requires managed Pi context attestations by default, coupling admission
to provider egress verification. The general Gate quickstarts opt out
explicitly. Keep the default, or pass `--require-pi-attestation`, for managed
Pi; use `--no-require-pi-attestation` only for an intentionally unmanaged deployment.
See the [managed Pi example](examples/pi-attested-admission/README.md) for the
matching Pi and OpenShell fork branches, startup contract, and current limits.

## Policy shape

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

```bash
uv run egress-gate --registry my_gates:registry gates list
uv run egress-gate --registry my_gates:registry serve
uv run egress-gate --registry my_gates:registry serve --no-require-pi-attestation
```

OpenShell owns interception, routing, and credential attachment. Egress Gate
Expand All @@ -103,11 +110,14 @@ from egress_gate.service import EgressGateServer
server = EgressGateServer(
create_builtin_registry(),
timeout_middleware_processing=10,
require_pi_attestation=False,
)
server.serve_sync("127.0.0.1:50051")
```

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EGRESS_GATE_HOST_IP=YOUR_HOST_IPV4
PI_MODELS_PATH=./models.json
PI_MODEL_API_KEY=your-provider-key
# Optional: omit this to start Pi in an empty workspace.
# PI_WORKSPACE_PATH=/absolute/path/to/your/project
251 changes: 251 additions & 0 deletions projects/egress-gate/examples/pi-attested-admission/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# Pi attested-admission example

This example runs the normal forked Pi CLI inside OpenShell and sends admitted
conversation context to the configured NVIDIA inference endpoint. One
endpoint-scoped provider and credential serve all configured models.

The example demonstrates the same policy at both context boundaries:

- `DENY_THIS` is rejected before Pi adds a user message or tool result to its
live context.
- `REDACT_THIS` becomes `[REDACTED]` before Pi adds or sends it.

The redaction case makes one real request to your configured endpoint and may
incur charges from that provider.

## Before you start

Use these matching fork branches:

- [Pi `johnny/before-user-message-commit`](https://github.com/johnnygreco/pi/tree/johnny/before-user-message-commit)
- [OpenShell `openshell/pi-egress-admission`](https://github.com/johnnygreco/OpenShell/tree/openshell/pi-egress-admission)
- [OpenShell Research integration branch](https://github.com/NVIDIA/OpenShell-Research/tree/johnny/pi-attested-admission)

You do not need to clone the Pi or OpenShell forks manually. The first
`./demo.sh prepare` clones both into the ignored local workspace
`projects/egress-gate/.workspaces/pi-attested-admission/`. Later runs update
them with fast-forward-only pulls, so the fork contents never appear as
OpenShell Research changes. To reuse a checkout elsewhere, set `PI_REPO` or
`OPENSHELL_REPO` to its absolute path.

The OpenShell gateway needs a running compute backend. On macOS, start Docker
Desktop and wait until `docker info` succeeds before running the gateway;
Podman is also supported. Building the gateway also requires Z3 (`brew install
z3` on macOS or `libz3-dev` on Debian and Ubuntu). The fork recommends `mise`
2026.4.25 or newer.

From the `OpenShell-Research` checkout, change to the example directory. Run
all remaining commands there:

```shell
cd projects/egress-gate/examples/pi-attested-admission
```

Create the local configuration file, replace every example value, and load it
into the current shell:

```shell
cp .env.example .env
# Edit .env before continuing.
set -a
source .env
set +a
```

`set -a` makes assignments loaded by `source .env` available to commands run
from this shell; `set +a` restores the shell's default behavior afterward.

If the model endpoint does not require authentication, set
`PI_MODEL_API_KEY=unused`. Source `.env` in the terminals that run `gateway` and
`reset`; the other actions do not consume the credential.

`PI_WORKSPACE_PATH` is optional. Set it to the absolute path of a project you
want Pi to work on. The reset step uploads its contents to `/sandbox/workspace`
using the project's normal `.gitignore` rules. If you omit it, Pi starts in an
empty `/sandbox/workspace`; no local files are copied. In either case, Pi starts
there, so project instructions, extensions, skills, prompts, and session
grouping follow its ordinary current-directory behavior.

`PI_MODELS_PATH` points to a standard Pi `models.json`. Relative paths are
resolved from this example directory. The checked-in [models.json](models.json)
defines one `attested-provider`, its endpoint and credential reference, and
these models:

| Model ID | Pi transport |
| --- | --- |
| `azure/anthropic/claude-opus-5` | OpenAI Chat Completions |
| `azure/openai/gpt-5.6-sol` | OpenAI Responses |
| `nvidia/qwen/qwen3.8-flash-next` | OpenAI Chat Completions |

Pi starts with the reasoning-capable Qwen model and `high` thinking from
[settings.json](settings.json). Use Pi's normal model picker to switch among all
three without creating another OpenShell provider. Qwen uses Chat Completions
reasoning controls, and GPT-5.6 Sol uses Responses reasoning.
The endpoint's Opus 5 alias currently rejects explicit adaptive-thinking
controls, so it runs with the endpoint's default thinking behavior.

To use another catalog for the same endpoint, copy `models.json`, edit it using
Pi's documented JSON format, and set `PI_MODELS_PATH` to that file. OpenShell
pins network and credential access independently of Pi. To change endpoints,
update the matching host and port explicitly in `models.json`, `policy.yaml`,
and `provider-profile.yaml`.

`EGRESS_GATE_HOST_IP` is the address OpenShell uses to reach Egress Gate on this
machine. It must be a reachable, non-loopback IPv4 address; do not use
`127.0.0.1`. The provider's `baseUrl` in `models.json` is the model endpoint Pi
will call. A model server running on this machine must likewise use a hostname
or address reachable from the sandbox rather than `localhost`.

The example checks in ordinary Pi and OpenShell configuration files. It uploads
`models.json` and `settings.json` unchanged to Pi's standard
`~/.pi/agent` directory. The only generated configuration is a copy of
`gateway-middleware.toml.example` with `EGRESS_GATE_HOST_IP` substituted for its
documented placeholder. If an action needs configuration that is missing, the
script prints the values required by that action and stops before doing work.

Preview the complete workflow before running anything:

```shell
./demo.sh --print all
```

The walkthrough lists the terminal sequence and configuration visible to the
current shell. To inspect the exact commands for one action, use its name—for
example, `./demo.sh --print prepare` or `./demo.sh --print launch`.

## Run the example

Prepare the forks and build and package the locally modified Pi agent core and
coding agent:

```shell
./demo.sh prepare
```

The updates use fast-forward-only pulls and stop instead of merging divergent
local work.

Keep Egress Gate running in one terminal:

```shell title="Terminal 1: Egress Gate"
./demo.sh serve
```

Start the matching OpenShell gateway in a second terminal:

```shell title="Terminal 2: OpenShell gateway"
./demo.sh gateway
```

The example uses its own gateway name and passes it explicitly to every
OpenShell command. It does not depend on or change your globally selected
OpenShell gateway.

After the gateway reports that it is ready, create the demo sandbox from a
third terminal. `reset` is deliberately named: it deletes any prior demo
sandbox and its sessions before uploading the current runtime, configuration,
and workspace.

```shell title="Terminal 3: Pi"
./demo.sh reset
```

Then launch Pi:

```shell title="Terminal 3: Pi"
./demo.sh launch
```

This executes the fork's normal `pi` entrypoint. The explicit
`PI_OPENSHELL_CONTEXT_ADMISSION=1` setting makes its built-in OpenShell
admission boundary mandatory for the session. `launch` only enters the existing
sandbox; it does not replace the sandbox or Pi's state. Exit and run `launch`
again to use Pi's normal `/resume` flow and persistent JSONL sessions. Run
`reset` only when you intentionally want a fresh sandbox or need to apply a new
runtime, policy, model configuration, credential, or workspace snapshot.

The sandbox image adds the `fd` and `rg` executables used by Pi's standard
`find` and `grep` tools. Pi itself still comes from the prepared fork package,
and starts without a wrapper or restrictive CLI flags. Its standard user and
project resource discovery, extension loading, tools, model picker, thinking
controls, compaction, and session manager remain active. OpenShell's filesystem
and network policy still apply to every process in the sandbox; arbitrary
package downloads are intentionally outside this endpoint-focused example.

The example registers an endpoint-specific provider profile using the host-side
`PI_MODEL_API_KEY`. Its `delivery: proxy` setting keeps the credential and any
resolver placeholder out of the sandbox. Pi sends the non-secret placeholder
declared by `models.json`; after admission and middleware processing succeed,
the OpenShell supervisor replaces that authorization header with the real,
endpoint-bound credential immediately before forwarding the request.

At the Pi prompt, submit both of these in the same session:

```text
Reply with exactly: DENY_THIS
```

```text
Reply with exactly: REDACT_THIS
```

The first submission is denied without starting a model request. The second
makes a request containing `[REDACTED]`.

To exercise tool-result admission without putting the marker in the user
message, ask Pi:

```text
Use bash to print the concatenation of DENY_ and THIS, then tell me the output.
```

The tool runs, but its result is replaced by Pi's protocol-safe blocked result
before it enters live context. Repeat with `REDACT_` and `THIS` to see the tool
result admitted as `[REDACTED]`.

Pi uses its standard session manager and JSONL session location, and exposes
the active path to tools as `PI_SESSION_FILE`. Admission runs before a user
message or tool result reaches that history. `launch` preserves the history;
`reset` and `cleanup` delete it with the sandbox.

## How it works

1. The forked `pi` entrypoint sees `PI_OPENSHELL_CONTEXT_ADMISSION=1` and installs
its built-in mandatory `ContextAdmission` boundary. Pi otherwise starts
normally, including its standard project and user extension discovery.
2. Pi calls that boundary for each rendered user message and finalized tool
result before it queues, appends, or persists the value.
3. The adapter sends the exact context addition to OpenShell's sandbox-local
bridge. Egress Gate applies `policy.yaml` and returns allow, deny, or a
complete replacement.
4. OpenShell keeps the signed attestation and gives Pi only an opaque handle.
The adapter keeps handles in its private closure, outside Pi messages.
5. Immediately before every provider request, Pi passes the exact outbound
context through admission. This includes normal turns, retries, compaction,
branch summaries, and contexts restored from a prior session. The adapter
applies any replacement and obtains a fresh handle for the newest user
message or tool result in that exact context.
6. OpenShell strips the handle, resolves the supervisor-held attestation, and
supplies it only to the configured Egress Gate middleware stage. Egress Gate
verifies the latest context addition and scans the complete provider request
before OpenShell injects the proxy-delivered model credential.

## Current scope

The attestation adapter supports normal text turns, text tool results, queued
steering and follow-up messages, retries, automatic model continuations,
compaction, branch summaries, and restored sessions using the OpenAI Chat
Completions and Responses wire formats. Image inputs are outside this example's
current scope and fail closed.

## Cleanup

Exit Pi, but leave the OpenShell gateway running while cleanup deletes the
sandbox and provider:

```shell
./demo.sh cleanup
```

Then stop the OpenShell gateway and Egress Gate with `Ctrl-C`. For another
session in the same prepared sandbox, use `./demo.sh launch` instead of cleanup.
Loading
Loading